From bc4079e58d8037e4208b97e96e2111f1a0ad1457 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 06:09:59 +0000 Subject: [PATCH 1/6] Initial plan From c371371331464b48f705b6c61fd7a8d4cf277806 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 06:17:05 +0000 Subject: [PATCH 2/6] Implement PostgreSQL range types support with all tests passing Co-authored-by: mathiasrw <1063454+mathiasrw@users.noreply.github.com> --- build.sh | 1 + src/60range.js | 227 ++++++++++++++++++++++++++++++++++++++++++++++ test/test055-B.js | 170 ++++++++++++++++++++++++++++++++++ 3 files changed, 398 insertions(+) create mode 100644 src/60range.js create mode 100644 test/test055-B.js diff --git a/build.sh b/build.sh index 4b64e3aa15..cb67f4310f 100755 --- a/build.sh +++ b/build.sh @@ -86,6 +86,7 @@ cat \ src/58json.js \ src/59convert.js \ src/60createtable.js \ + src/60range.js \ src/61date.js \ src/62droptable.js \ src/63createvertex.js \ diff --git a/src/60range.js b/src/60range.js new file mode 100644 index 0000000000..715429a3e6 --- /dev/null +++ b/src/60range.js @@ -0,0 +1,227 @@ +/* +// +// PostgreSQL Range Types Implementation +// Date: 2025-12-07 +// (c) AlaSQL Contributors +// +*/ + +// Range class to represent a range of values +alasql.Range = function (lower, upper, lowerInc, upperInc) { + this.lower = lower; + this.upper = upper; + // Default: inclusive lower bound, exclusive upper bound [lower, upper) + this.lowerInc = lowerInc !== undefined ? lowerInc : true; + this.upperInc = upperInc !== undefined ? upperInc : false; +}; + +alasql.Range.prototype.isEmpty = function () { + if (this.lower === undefined || this.upper === undefined) return true; + if (this.lower > this.upper) return true; + if (this.lower === this.upper && (!this.lowerInc || !this.upperInc)) return true; + return false; +}; + +alasql.Range.prototype.contains = function (value) { + if (this.isEmpty()) return false; + var lowerOk = this.lowerInc ? this.lower <= value : this.lower < value; + var upperOk = this.upperInc ? value <= this.upper : value < this.upper; + return lowerOk && upperOk; +}; + +alasql.Range.prototype.overlaps = function (other) { + if (this.isEmpty() || other.isEmpty()) return false; + // Ranges overlap if one contains the start or end of the other + return ( + this.contains(other.lower) || + this.contains(other.upper) || + other.contains(this.lower) || + other.contains(this.upper) || + (this.lower <= other.lower && this.upper >= other.upper) || + (other.lower <= this.lower && other.upper >= this.upper) + ); +}; + +alasql.Range.prototype.union = function (other) { + if (this.isEmpty()) return other; + if (other.isEmpty()) return this; + + var lower = Math.min(this.lower, other.lower); + var upper = Math.max(this.upper, other.upper); + var lowerInc = + this.lower < other.lower + ? this.lowerInc + : this.lower > other.lower + ? other.lowerInc + : this.lowerInc || other.lowerInc; + var upperInc = + this.upper > other.upper + ? this.upperInc + : this.upper < other.upper + ? other.upperInc + : this.upperInc || other.upperInc; + + return new alasql.Range(lower, upper, lowerInc, upperInc); +}; + +alasql.Range.prototype.intersection = function (other) { + if (this.isEmpty() || other.isEmpty()) return null; + if (!this.overlaps(other)) return null; + + var lower = Math.max(this.lower, other.lower); + var upper = Math.min(this.upper, other.upper); + var lowerInc = + this.lower > other.lower + ? this.lowerInc + : this.lower < other.lower + ? other.lowerInc + : this.lowerInc && other.lowerInc; + var upperInc = + this.upper < other.upper + ? this.upperInc + : this.upper > other.upper + ? other.upperInc + : this.upperInc && other.upperInc; + + var result = new alasql.Range(lower, upper, lowerInc, upperInc); + return result.isEmpty() ? null : result; +}; + +alasql.Range.prototype.difference = function (other) { + if (this.isEmpty()) return null; + if (other.isEmpty()) return this; + if (!this.overlaps(other)) return this; + + // If other completely contains this, return null + if (other.lower <= this.lower && other.upper >= this.upper) { + var thisLowerIn = + other.lower === this.lower ? other.lowerInc && this.lowerInc : other.lower < this.lower; + var thisUpperIn = + other.upper === this.upper ? other.upperInc && this.upperInc : other.upper > this.upper; + if (thisLowerIn && thisUpperIn) return null; + } + + // Return the portion before other starts + if (this.lower < other.lower) { + var upperInc = !other.lowerInc; + return new alasql.Range(this.lower, other.lower, this.lowerInc, upperInc); + } + + // Return the portion after other ends + if (this.upper > other.upper) { + var lowerInc = !other.upperInc; + return new alasql.Range(other.upper, this.upper, lowerInc, this.upperInc); + } + + return null; +}; + +alasql.Range.prototype.isSubsetOf = function (other) { + if (this.isEmpty()) return true; + if (other.isEmpty()) return false; + + var lowerOk = + this.lower > other.lower || (this.lower === other.lower && (!this.lowerInc || other.lowerInc)); + var upperOk = + this.upper < other.upper || (this.upper === other.upper && (!this.upperInc || other.upperInc)); + + return lowerOk && upperOk; +}; + +alasql.Range.prototype.isSupersetOf = function (other) { + return other.isSubsetOf(this); +}; + +alasql.Range.prototype.isDisjointFrom = function (other) { + return !this.overlaps(other); +}; + +// Range constructor functions + +// Integer range (int4range) +stdfn.INT4RANGE = function (lower, upper, lowerInc, upperInc) { + return new alasql.Range(lower, upper, lowerInc, upperInc); +}; + +// Big integer range (int8range) +stdfn.INT8RANGE = function (lower, upper, lowerInc, upperInc) { + return new alasql.Range(lower, upper, lowerInc, upperInc); +}; + +// Numeric range (numrange) +stdfn.NUMRANGE = function (lower, upper, lowerInc, upperInc) { + return new alasql.Range(lower, upper, lowerInc, upperInc); +}; + +// Timestamp range (tsrange) +stdfn.TSRANGE = function (lower, upper, lowerInc, upperInc) { + return new alasql.Range(lower, upper, lowerInc, upperInc); +}; + +// Timestamp with timezone range (tstzrange) +stdfn.TSTZRANGE = function (lower, upper, lowerInc, upperInc) { + return new alasql.Range(lower, upper, lowerInc, upperInc); +}; + +// Date range (daterange) +stdfn.DATERANGE = function (lower, upper, lowerInc, upperInc) { + return new alasql.Range(lower, upper, lowerInc, upperInc); +}; + +// Range operation functions + +// Check if ranges overlap +stdfn.RANGE_OVERLAPS = function (range1, range2) { + if (!range1 || !range2) return false; + return range1.overlaps(range2); +}; + +// Check if range contains element +stdfn.RANGE_CONTAINS = function (range, element) { + if (!range) return false; + return range.contains(element); +}; + +// Check if range1 contains range2 +stdfn.RANGE_CONTAINS_RANGE = function (range1, range2) { + if (!range1 || !range2) return false; + return range1.isSupersetOf(range2); +}; + +// Union of two ranges +stdfn.RANGE_UNION = function (range1, range2) { + if (!range1) return range2; + if (!range2) return range1; + return range1.union(range2); +}; + +// Intersection of two ranges +stdfn.RANGE_INTERSECTION = function (range1, range2) { + if (!range1 || !range2) return null; + return range1.intersection(range2); +}; + +// Difference of two ranges +stdfn.RANGE_DIFFERENCE = function (range1, range2) { + if (!range1) return null; + if (!range2) return range1; + return range1.difference(range2); +}; + +// Check if range1 is subset of range2 +stdfn.RANGE_IS_SUBSET = function (range1, range2) { + if (!range1 || !range2) return false; + return range1.isSubsetOf(range2); +}; + +// Check if range1 is superset of range2 +stdfn.RANGE_IS_SUPERSET = function (range1, range2) { + if (!range1 || !range2) return false; + return range1.isSupersetOf(range2); +}; + +// Check if ranges are disjoint +stdfn.RANGE_IS_DISJOINT = function (range1, range2) { + if (!range1 || !range2) return true; + return range1.isDisjointFrom(range2); +}; diff --git a/test/test055-B.js b/test/test055-B.js new file mode 100644 index 0000000000..7439f3943c --- /dev/null +++ b/test/test055-B.js @@ -0,0 +1,170 @@ +if (typeof exports === 'object') { + var assert = require('assert'); + var alasql = require('..'); +} + +describe('Test 055-B - PostgreSQL Range Types', function () { + const test = '055B'; + + before(function () { + alasql('create database test' + test); + alasql('use test' + test); + }); + + after(function () { + alasql('drop database test' + test); + }); + + it('A) Create integer ranges', function () { + var r1 = alasql('SELECT int4range(10, 20) as r')[0].r; + assert(r1); + assert.equal(r1.lower, 10); + assert.equal(r1.upper, 20); + }); + + it('B) Create numeric ranges', function () { + var r1 = alasql('SELECT numrange(11.1, 22.2) as r')[0].r; + assert(r1); + assert.equal(r1.lower, 11.1); + assert.equal(r1.upper, 22.2); + }); + + it('C) Create date ranges', function () { + var d1 = new Date('2020-01-01'); + var d2 = new Date('2020-12-31'); + var r1 = alasql('SELECT daterange(?, ?) as r', [d1, d2])[0].r; + assert(r1); + // Range object should have the dates + assert(r1.lower); + assert(r1.upper); + assert.equal(r1.lower.getFullYear(), 2020); + assert.equal(r1.upper.getFullYear(), 2020); + }); + + it('D) Test range_overlaps - overlapping ranges', function () { + var res = alasql('SELECT range_overlaps(int4range(10, 20), int4range(15, 25)) as r')[0].r; + assert.equal(res, true); + }); + + it('E) Test range_overlaps - non-overlapping ranges', function () { + var res = alasql('SELECT range_overlaps(int4range(10, 20), int4range(25, 30)) as r')[0].r; + assert.equal(res, false); + }); + + it('F) Test range_contains - element in range', function () { + var res = alasql('SELECT range_contains(int4range(10, 20), 15) as r')[0].r; + assert.equal(res, true); + }); + + it('G) Test range_contains - element not in range', function () { + var res = alasql('SELECT range_contains(int4range(10, 20), 25) as r')[0].r; + assert.equal(res, false); + }); + + it('H) Test range_contains_range - subset', function () { + var res = alasql('SELECT range_contains_range(int4range(10, 30), int4range(15, 25)) as r')[0].r; + assert.equal(res, true); + }); + + it('I) Test range_contains_range - not subset', function () { + var res = alasql('SELECT range_contains_range(int4range(10, 20), int4range(15, 25)) as r')[0].r; + assert.equal(res, false); + }); + + it('J) Test range_union', function () { + var r = alasql('SELECT range_union(int4range(10, 20), int4range(15, 25)) as r')[0].r; + assert(r); + assert.equal(r.lower, 10); + assert.equal(r.upper, 25); + }); + + it('K) Test range_intersection', function () { + var r = alasql('SELECT range_intersection(int4range(10, 20), int4range(15, 25)) as r')[0].r; + assert(r); + assert.equal(r.lower, 15); + assert.equal(r.upper, 20); + }); + + it('L) Test range_intersection - no overlap returns null', function () { + var r = alasql('SELECT range_intersection(int4range(10, 20), int4range(25, 30)) as r')[0].r; + assert.equal(r, null); + }); + + it('M) Test range_difference', function () { + var r = alasql('SELECT range_difference(int4range(10, 30), int4range(20, 40)) as r')[0].r; + assert(r); + // Difference should give [10, 20) + assert.equal(r.lower, 10); + assert.equal(r.upper, 20); + }); + + it('N) Use range in table and query', function () { + alasql('CREATE TABLE events (id int, period range)'); + alasql('INSERT INTO events VALUES (1, int4range(10, 20))'); + alasql('INSERT INTO events VALUES (2, int4range(15, 25))'); + alasql('INSERT INTO events VALUES (3, int4range(30, 40))'); + + // Find events overlapping with [12, 18] + var res = alasql('SELECT * FROM events WHERE range_overlaps(period, int4range(12, 18))'); + assert.equal(res.length, 2); + assert.equal(res[0].id, 1); + assert.equal(res[1].id, 2); + }); + + it('O) Range with inclusive/exclusive bounds', function () { + // Test default bounds (inclusive lower, exclusive upper) + var r1 = alasql('SELECT int4range(10, 20) as r')[0].r; + assert.equal(r1.lowerInc, true); + assert.equal(r1.upperInc, false); + }); + + it('P) Range empty check', function () { + var r1 = alasql('SELECT int4range(10, 10) as r')[0].r; + assert.equal(r1.isEmpty(), true); + + var r2 = alasql('SELECT int4range(10, 20) as r')[0].r; + assert.equal(r2.isEmpty(), false); + }); + + it('Q) Test isSubsetOf method', function () { + var res = alasql('SELECT range_is_subset(int4range(15, 20), int4range(10, 30)) as r')[0].r; + assert.equal(res, true); + + var res2 = alasql('SELECT range_is_subset(int4range(5, 20), int4range(10, 30)) as r')[0].r; + assert.equal(res2, false); + }); + + it('R) Test isSupersetOf method', function () { + var res = alasql('SELECT range_is_superset(int4range(10, 30), int4range(15, 20)) as r')[0].r; + assert.equal(res, true); + + var res2 = alasql('SELECT range_is_superset(int4range(10, 25), int4range(15, 30)) as r')[0].r; + assert.equal(res2, false); + }); + + it('S) Test isDisjointFrom method', function () { + var res = alasql('SELECT range_is_disjoint(int4range(10, 20), int4range(25, 30)) as r')[0].r; + assert.equal(res, true); + + var res2 = alasql('SELECT range_is_disjoint(int4range(10, 20), int4range(15, 25)) as r')[0].r; + assert.equal(res2, false); + }); + + it('T) Test int8range (bigint range)', function () { + var r = alasql('SELECT int8range(1000000, 2000000) as r')[0].r; + assert(r); + assert.equal(r.lower, 1000000); + assert.equal(r.upper, 2000000); + }); + + it('U) Test tsrange (timestamp range)', function () { + var t1 = new Date('2020-01-01T10:00:00'); + var t2 = new Date('2020-01-01T12:00:00'); + var r = alasql('SELECT tsrange(?, ?) as r', [t1, t2])[0].r; + assert(r); + assert(r.lower); + assert(r.upper); + assert.equal(r.lower.getHours(), 10); + assert.equal(r.upper.getHours(), 12); + }); +}); From 2c8df46e27a11380aeb33283fadc047742e35803 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 06:19:53 +0000 Subject: [PATCH 3/6] Fix range overlap logic and add clarifying comments Co-authored-by: mathiasrw <1063454+mathiasrw@users.noreply.github.com> --- src/60range.js | 17 ++++++++--------- test/test055-B.js | 2 ++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/60range.js b/src/60range.js index 715429a3e6..a1358f6be9 100644 --- a/src/60range.js +++ b/src/60range.js @@ -31,15 +31,14 @@ alasql.Range.prototype.contains = function (value) { alasql.Range.prototype.overlaps = function (other) { if (this.isEmpty() || other.isEmpty()) return false; - // Ranges overlap if one contains the start or end of the other - return ( - this.contains(other.lower) || - this.contains(other.upper) || - other.contains(this.lower) || - other.contains(this.upper) || - (this.lower <= other.lower && this.upper >= other.upper) || - (other.lower <= this.lower && other.upper >= this.upper) - ); + // Ranges overlap if they are not disjoint + // They are disjoint if one ends before the other starts + if (this.upper < other.lower) return false; + if (other.upper < this.lower) return false; + // Handle boundary cases where bounds are equal but exclusive + if (this.upper === other.lower && (!this.upperInc || !other.lowerInc)) return false; + if (other.upper === this.lower && (!other.upperInc || !this.lowerInc)) return false; + return true; }; alasql.Range.prototype.union = function (other) { diff --git a/test/test055-B.js b/test/test055-B.js index 7439f3943c..e8cb58fd84 100644 --- a/test/test055-B.js +++ b/test/test055-B.js @@ -99,6 +99,8 @@ describe('Test 055-B - PostgreSQL Range Types', function () { }); it('N) Use range in table and query', function () { + // Note: AlaSQL is schemaless, so 'range' here is just a label + // The actual Range object is stored as-is alasql('CREATE TABLE events (id int, period range)'); alasql('INSERT INTO events VALUES (1, int4range(10, 20))'); alasql('INSERT INTO events VALUES (2, int4range(15, 25))'); From 9fa28491cb6a933e00f96a99d53739ff8d9e26f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 06:26:47 +0000 Subject: [PATCH 4/6] Use explicit comparison functions for Date objects compatibility Co-authored-by: mathiasrw <1063454+mathiasrw@users.noreply.github.com> --- src/60range.js | 105 +++++++++++++++++++++++++++++-------------------- 1 file changed, 62 insertions(+), 43 deletions(-) diff --git a/src/60range.js b/src/60range.js index a1358f6be9..ee7a006dca 100644 --- a/src/60range.js +++ b/src/60range.js @@ -6,6 +6,26 @@ // */ +// Helper functions for comparing values (including dates) +var compareValues = function (a, b) { + // Handle Date objects explicitly + if (a instanceof Date && b instanceof Date) { + return a.getTime() - b.getTime(); + } + // For numbers and other comparable types + if (a < b) return -1; + if (a > b) return 1; + return 0; +}; + +var minValue = function (a, b) { + return compareValues(a, b) <= 0 ? a : b; +}; + +var maxValue = function (a, b) { + return compareValues(a, b) >= 0 ? a : b; +}; + // Range class to represent a range of values alasql.Range = function (lower, upper, lowerInc, upperInc) { this.lower = lower; @@ -17,15 +37,18 @@ alasql.Range = function (lower, upper, lowerInc, upperInc) { alasql.Range.prototype.isEmpty = function () { if (this.lower === undefined || this.upper === undefined) return true; - if (this.lower > this.upper) return true; - if (this.lower === this.upper && (!this.lowerInc || !this.upperInc)) return true; + var cmp = compareValues(this.lower, this.upper); + if (cmp > 0) return true; + if (cmp === 0 && (!this.lowerInc || !this.upperInc)) return true; return false; }; alasql.Range.prototype.contains = function (value) { if (this.isEmpty()) return false; - var lowerOk = this.lowerInc ? this.lower <= value : this.lower < value; - var upperOk = this.upperInc ? value <= this.upper : value < this.upper; + var lowerCmp = compareValues(this.lower, value); + var upperCmp = compareValues(value, this.upper); + var lowerOk = this.lowerInc ? lowerCmp <= 0 : lowerCmp < 0; + var upperOk = this.upperInc ? upperCmp <= 0 : upperCmp < 0; return lowerOk && upperOk; }; @@ -33,11 +56,13 @@ alasql.Range.prototype.overlaps = function (other) { if (this.isEmpty() || other.isEmpty()) return false; // Ranges overlap if they are not disjoint // They are disjoint if one ends before the other starts - if (this.upper < other.lower) return false; - if (other.upper < this.lower) return false; + var cmp1 = compareValues(this.upper, other.lower); + var cmp2 = compareValues(other.upper, this.lower); + if (cmp1 < 0) return false; + if (cmp2 < 0) return false; // Handle boundary cases where bounds are equal but exclusive - if (this.upper === other.lower && (!this.upperInc || !other.lowerInc)) return false; - if (other.upper === this.lower && (!other.upperInc || !this.lowerInc)) return false; + if (cmp1 === 0 && (!this.upperInc || !other.lowerInc)) return false; + if (cmp2 === 0 && (!other.upperInc || !this.lowerInc)) return false; return true; }; @@ -45,20 +70,15 @@ alasql.Range.prototype.union = function (other) { if (this.isEmpty()) return other; if (other.isEmpty()) return this; - var lower = Math.min(this.lower, other.lower); - var upper = Math.max(this.upper, other.upper); + var lower = minValue(this.lower, other.lower); + var upper = maxValue(this.upper, other.upper); + var lowerCmp = compareValues(this.lower, other.lower); + var upperCmp = compareValues(this.upper, other.upper); + var lowerInc = - this.lower < other.lower - ? this.lowerInc - : this.lower > other.lower - ? other.lowerInc - : this.lowerInc || other.lowerInc; + lowerCmp < 0 ? this.lowerInc : lowerCmp > 0 ? other.lowerInc : this.lowerInc || other.lowerInc; var upperInc = - this.upper > other.upper - ? this.upperInc - : this.upper < other.upper - ? other.upperInc - : this.upperInc || other.upperInc; + upperCmp > 0 ? this.upperInc : upperCmp < 0 ? other.upperInc : this.upperInc || other.upperInc; return new alasql.Range(lower, upper, lowerInc, upperInc); }; @@ -67,20 +87,15 @@ alasql.Range.prototype.intersection = function (other) { if (this.isEmpty() || other.isEmpty()) return null; if (!this.overlaps(other)) return null; - var lower = Math.max(this.lower, other.lower); - var upper = Math.min(this.upper, other.upper); + var lower = maxValue(this.lower, other.lower); + var upper = minValue(this.upper, other.upper); + var lowerCmp = compareValues(this.lower, other.lower); + var upperCmp = compareValues(this.upper, other.upper); + var lowerInc = - this.lower > other.lower - ? this.lowerInc - : this.lower < other.lower - ? other.lowerInc - : this.lowerInc && other.lowerInc; + lowerCmp > 0 ? this.lowerInc : lowerCmp < 0 ? other.lowerInc : this.lowerInc && other.lowerInc; var upperInc = - this.upper < other.upper - ? this.upperInc - : this.upper > other.upper - ? other.upperInc - : this.upperInc && other.upperInc; + upperCmp < 0 ? this.upperInc : upperCmp > 0 ? other.upperInc : this.upperInc && other.upperInc; var result = new alasql.Range(lower, upper, lowerInc, upperInc); return result.isEmpty() ? null : result; @@ -92,22 +107,25 @@ alasql.Range.prototype.difference = function (other) { if (!this.overlaps(other)) return this; // If other completely contains this, return null - if (other.lower <= this.lower && other.upper >= this.upper) { - var thisLowerIn = - other.lower === this.lower ? other.lowerInc && this.lowerInc : other.lower < this.lower; - var thisUpperIn = - other.upper === this.upper ? other.upperInc && this.upperInc : other.upper > this.upper; + var lowerCmp1 = compareValues(other.lower, this.lower); + var upperCmp1 = compareValues(other.upper, this.upper); + + if (lowerCmp1 <= 0 && upperCmp1 >= 0) { + var thisLowerIn = lowerCmp1 === 0 ? other.lowerInc && this.lowerInc : lowerCmp1 < 0; + var thisUpperIn = upperCmp1 === 0 ? other.upperInc && this.upperInc : upperCmp1 > 0; if (thisLowerIn && thisUpperIn) return null; } // Return the portion before other starts - if (this.lower < other.lower) { + var lowerCmp2 = compareValues(this.lower, other.lower); + if (lowerCmp2 < 0) { var upperInc = !other.lowerInc; return new alasql.Range(this.lower, other.lower, this.lowerInc, upperInc); } // Return the portion after other ends - if (this.upper > other.upper) { + var upperCmp2 = compareValues(this.upper, other.upper); + if (upperCmp2 > 0) { var lowerInc = !other.upperInc; return new alasql.Range(other.upper, this.upper, lowerInc, this.upperInc); } @@ -119,10 +137,11 @@ alasql.Range.prototype.isSubsetOf = function (other) { if (this.isEmpty()) return true; if (other.isEmpty()) return false; - var lowerOk = - this.lower > other.lower || (this.lower === other.lower && (!this.lowerInc || other.lowerInc)); - var upperOk = - this.upper < other.upper || (this.upper === other.upper && (!this.upperInc || other.upperInc)); + var lowerCmp = compareValues(this.lower, other.lower); + var upperCmp = compareValues(this.upper, other.upper); + + var lowerOk = lowerCmp > 0 || (lowerCmp === 0 && (!this.lowerInc || other.lowerInc)); + var upperOk = upperCmp < 0 || (upperCmp === 0 && (!this.upperInc || other.upperInc)); return lowerOk && upperOk; }; From ff390d402d798d77157ac97588524e0aa28b341a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 06:28:46 +0000 Subject: [PATCH 5/6] Improve null handling in range_is_disjoint and clarify comments Co-authored-by: mathiasrw <1063454+mathiasrw@users.noreply.github.com> --- src/60range.js | 2 +- test/test055-B.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/60range.js b/src/60range.js index ee7a006dca..ecf4ca32ab 100644 --- a/src/60range.js +++ b/src/60range.js @@ -240,6 +240,6 @@ stdfn.RANGE_IS_SUPERSET = function (range1, range2) { // Check if ranges are disjoint stdfn.RANGE_IS_DISJOINT = function (range1, range2) { - if (!range1 || !range2) return true; + if (!range1 || !range2) return false; return range1.isDisjointFrom(range2); }; diff --git a/test/test055-B.js b/test/test055-B.js index e8cb58fd84..39755d985a 100644 --- a/test/test055-B.js +++ b/test/test055-B.js @@ -99,8 +99,8 @@ describe('Test 055-B - PostgreSQL Range Types', function () { }); it('N) Use range in table and query', function () { - // Note: AlaSQL is schemaless, so 'range' here is just a label - // The actual Range object is stored as-is + // Note: AlaSQL treats 'range' as a generic column type that can store any JavaScript object + // In this case, it stores Range instances created by range constructor functions alasql('CREATE TABLE events (id int, period range)'); alasql('INSERT INTO events VALUES (1, int4range(10, 20))'); alasql('INSERT INTO events VALUES (2, int4range(15, 25))'); From b27778cce48c9b9c17b92eb2e51360d226a854c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Dec 2025 14:55:36 +0000 Subject: [PATCH 6/6] Use deepEqual in tests and simplify comparison functions Co-authored-by: mathiasrw <1063454+mathiasrw@users.noreply.github.com> --- src/60range.js | 10 +++------- test/test055-B.js | 24 ++++++------------------ 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/src/60range.js b/src/60range.js index ecf4ca32ab..0b9f8fb115 100644 --- a/src/60range.js +++ b/src/60range.js @@ -7,23 +7,19 @@ */ // Helper functions for comparing values (including dates) +// JavaScript's comparison operators work with valueOf() for both Date and Number types var compareValues = function (a, b) { - // Handle Date objects explicitly - if (a instanceof Date && b instanceof Date) { - return a.getTime() - b.getTime(); - } - // For numbers and other comparable types if (a < b) return -1; if (a > b) return 1; return 0; }; var minValue = function (a, b) { - return compareValues(a, b) <= 0 ? a : b; + return a <= b ? a : b; }; var maxValue = function (a, b) { - return compareValues(a, b) >= 0 ? a : b; + return a >= b ? a : b; }; // Range class to represent a range of values diff --git a/test/test055-B.js b/test/test055-B.js index 39755d985a..f1bda5d178 100644 --- a/test/test055-B.js +++ b/test/test055-B.js @@ -17,16 +17,12 @@ describe('Test 055-B - PostgreSQL Range Types', function () { it('A) Create integer ranges', function () { var r1 = alasql('SELECT int4range(10, 20) as r')[0].r; - assert(r1); - assert.equal(r1.lower, 10); - assert.equal(r1.upper, 20); + assert.deepEqual(r1, {lower: 10, upper: 20, lowerInc: true, upperInc: false}); }); it('B) Create numeric ranges', function () { var r1 = alasql('SELECT numrange(11.1, 22.2) as r')[0].r; - assert(r1); - assert.equal(r1.lower, 11.1); - assert.equal(r1.upper, 22.2); + assert.deepEqual(r1, {lower: 11.1, upper: 22.2, lowerInc: true, upperInc: false}); }); it('C) Create date ranges', function () { @@ -73,16 +69,12 @@ describe('Test 055-B - PostgreSQL Range Types', function () { it('J) Test range_union', function () { var r = alasql('SELECT range_union(int4range(10, 20), int4range(15, 25)) as r')[0].r; - assert(r); - assert.equal(r.lower, 10); - assert.equal(r.upper, 25); + assert.deepEqual(r, {lower: 10, upper: 25, lowerInc: true, upperInc: false}); }); it('K) Test range_intersection', function () { var r = alasql('SELECT range_intersection(int4range(10, 20), int4range(15, 25)) as r')[0].r; - assert(r); - assert.equal(r.lower, 15); - assert.equal(r.upper, 20); + assert.deepEqual(r, {lower: 15, upper: 20, lowerInc: true, upperInc: false}); }); it('L) Test range_intersection - no overlap returns null', function () { @@ -92,10 +84,8 @@ describe('Test 055-B - PostgreSQL Range Types', function () { it('M) Test range_difference', function () { var r = alasql('SELECT range_difference(int4range(10, 30), int4range(20, 40)) as r')[0].r; - assert(r); // Difference should give [10, 20) - assert.equal(r.lower, 10); - assert.equal(r.upper, 20); + assert.deepEqual(r, {lower: 10, upper: 20, lowerInc: true, upperInc: false}); }); it('N) Use range in table and query', function () { @@ -154,9 +144,7 @@ describe('Test 055-B - PostgreSQL Range Types', function () { it('T) Test int8range (bigint range)', function () { var r = alasql('SELECT int8range(1000000, 2000000) as r')[0].r; - assert(r); - assert.equal(r.lower, 1000000); - assert.equal(r.upper, 2000000); + assert.deepEqual(r, {lower: 1000000, upper: 2000000, lowerInc: true, upperInc: false}); }); it('U) Test tsrange (timestamp range)', function () {