Skip to content

Commit 353318d

Browse files
author
Lars-Erik Roald
committed
Merge branch 'columColumnFilter' into countFiler
2 parents 3c9bf0d + 3d59e31 commit 353318d

File tree

14 files changed

+694
-104
lines changed

14 files changed

+694
-104
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,6 +1589,33 @@ async function getRows() {
15891589
});
15901590
}
15911591
```
1592+
__Column-to-column filters__
1593+
You can compare one column to another column instead of comparing to a constant value.
1594+
This works both on the same table and across relations.
1595+
1596+
```javascript
1597+
import map from './map';
1598+
const db = map.sqlite('demo.db');
1599+
1600+
getRows();
1601+
1602+
async function getRows() {
1603+
// equality between related columns
1604+
const sameName = await db.order.getMany({
1605+
where: x => x.deliveryAddress.name.eq(x.customer.name)
1606+
});
1607+
1608+
// string pattern match against another column
1609+
const containsName = await db.order.getMany({
1610+
where: x => x.deliveryAddress.name.contains(x.customer.name)
1611+
});
1612+
1613+
// column as one of the bounds in between
1614+
const withColumnBound = await db.customer.getMany({
1615+
where: x => x.balance.between(x.id, 180)
1616+
});
1617+
}
1618+
```
15921619
__In__
15931620
```javascript
15941621
import map from './map';

package-lock.json

Lines changed: 278 additions & 68 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/client/index.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,12 +1064,20 @@ function groupByAggregate(path, arg) {
10641064
}
10651065
}
10661066

1067+
const isColumnProxyKey = '__isColumnProxy';
1068+
const columnPathKey = '__columnPath';
1069+
const columnRefKey = '__columnRef';
1070+
10671071
function column(path, ...previous) {
10681072
function c() {
10691073
let args = [];
10701074
for (let i = 0; i < arguments.length; i++) {
1071-
if (typeof arguments[i] === 'function')
1072-
args[i] = arguments[i](tableProxy(path.split('.').slice(0, -1).join('.')));
1075+
if (typeof arguments[i] === 'function') {
1076+
if (arguments[i][isColumnProxyKey])
1077+
args[i] = { [columnRefKey]: arguments[i][columnPathKey] };
1078+
else
1079+
args[i] = arguments[i](tableProxy(path.split('.').slice(0, -1).join('.')));
1080+
}
10731081
else
10741082
args[i] = arguments[i];
10751083
}
@@ -1092,6 +1100,10 @@ function column(path, ...previous) {
10921100
}
10931101
let handler = {
10941102
get(_target, property) {
1103+
if (property === isColumnProxyKey)
1104+
return true;
1105+
if (property === columnPathKey)
1106+
return path;
10951107
if (property === 'toJSON')
10961108
return Reflect.get(...arguments);
10971109
else if (property === 'then')

src/hostExpress/executePath.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ function _executePath(context, ...rest) {
114114
}
115115
return result;
116116
}
117+
else if (isColumnRef(json)) {
118+
return resolveColumnRef(table, json.__columnRef);
119+
}
117120
return json;
118121

119122
function tryGetAnyAllNone(path, table) {
@@ -476,6 +479,27 @@ function _executePath(context, ...rest) {
476479
return json instanceof Object && 'path' in json && 'args' in json;
477480
}
478481

482+
function isColumnRef(json) {
483+
return json instanceof Object && typeof json.__columnRef === 'string';
484+
}
485+
486+
function resolveColumnRef(table, path) {
487+
let current = table;
488+
const parts = path.split('.');
489+
for (let i = 0; i < parts.length; i++) {
490+
if (current)
491+
current = current[parts[i]];
492+
}
493+
494+
if (!current || typeof current._toFilterArg !== 'function') {
495+
let e = new Error(`Column reference '${path}' is invalid`);
496+
// @ts-ignore
497+
e.status = 400;
498+
throw e;
499+
}
500+
return current;
501+
}
502+
479503
function setSafe(o) {
480504
if (o instanceof Object)
481505
Object.defineProperty(o, 'isSafe', {

src/map2.d.ts

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -75,34 +75,40 @@ export interface Filter extends RawFilter {
7575
not(): Filter;
7676
}
7777

78-
type StringOnlyMethods = {
79-
startsWith(value: string | null | undefined): Filter;
80-
iStartsWith(value: string | null | undefined): Filter;
81-
endsWith(value: string | null | undefined): Filter;
82-
iEndsWith(value: string | null | undefined): Filter;
83-
contains(value: string | null | undefined): Filter;
84-
iContains(value: string | null | undefined): Filter;
85-
iEqual(value: string | null | undefined): Filter;
86-
ieq(value: string | null | undefined): Filter;
78+
type ComparableValue<Val, ColumnType = any> =
79+
| Val
80+
| null
81+
| undefined
82+
| ColumnFilterType<any, ColumnType>;
83+
84+
type StringOnlyMethods<ColumnType = 'string'> = {
85+
startsWith(value: ComparableValue<string, ColumnType>): Filter;
86+
iStartsWith(value: ComparableValue<string, ColumnType>): Filter;
87+
endsWith(value: ComparableValue<string, ColumnType>): Filter;
88+
iEndsWith(value: ComparableValue<string, ColumnType>): Filter;
89+
contains(value: ComparableValue<string, ColumnType>): Filter;
90+
iContains(value: ComparableValue<string, ColumnType>): Filter;
91+
iEqual(value: ComparableValue<string, ColumnType>): Filter;
92+
ieq(value: ComparableValue<string, ColumnType>): Filter;
8793
};
8894

8995
export type ColumnFilterType<Val, ColumnType = any> = {
90-
equal(value: Val | null | undefined): Filter;
91-
eq(value: Val | null | undefined): Filter;
92-
notEqual(value: Val | null | undefined): Filter;
93-
ne(value: Val | null | undefined): Filter;
94-
lessThan(value: Val | null | undefined): Filter;
95-
lt(value: Val | null | undefined): Filter;
96-
lessThanOrEqual(value: Val | null | undefined): Filter;
97-
le(value: Val | null | undefined): Filter;
98-
greaterThan(value: Val | null | undefined): Filter;
99-
gt(value: Val | null | undefined): Filter;
100-
greaterThanOrEqual(value: Val | null | undefined): Filter;
101-
ge(value: Val | null | undefined): Filter;
102-
in(values: readonly (Val | null | undefined)[]): Filter;
103-
between(from: Val | null | undefined, to: Val | null | undefined): Filter;
104-
notIn(values: readonly (Val | null | undefined)[]): Filter;
105-
} & (ColumnType extends 'string' ? StringOnlyMethods : {});
96+
equal(value: ComparableValue<Val, ColumnType>): Filter;
97+
eq(value: ComparableValue<Val, ColumnType>): Filter;
98+
notEqual(value: ComparableValue<Val, ColumnType>): Filter;
99+
ne(value: ComparableValue<Val, ColumnType>): Filter;
100+
lessThan(value: ComparableValue<Val, ColumnType>): Filter;
101+
lt(value: ComparableValue<Val, ColumnType>): Filter;
102+
lessThanOrEqual(value: ComparableValue<Val, ColumnType>): Filter;
103+
le(value: ComparableValue<Val, ColumnType>): Filter;
104+
greaterThan(value: ComparableValue<Val, ColumnType>): Filter;
105+
gt(value: ComparableValue<Val, ColumnType>): Filter;
106+
greaterThanOrEqual(value: ComparableValue<Val, ColumnType>): Filter;
107+
ge(value: ComparableValue<Val, ColumnType>): Filter;
108+
in(values: readonly ComparableValue<Val, ColumnType>[]): Filter;
109+
between(from: ComparableValue<Val, ColumnType>, to: ComparableValue<Val, ColumnType>): Filter;
110+
notIn(values: readonly ComparableValue<Val, ColumnType>[]): Filter;
111+
} & (ColumnType extends 'string' ? StringOnlyMethods<ColumnType> : {});
106112

107113
export type JsonArray = Array<JsonValue>;
108114
export type JsonObject = { [key: string]: JsonValue };
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
function encodeFilterArg(context, column, arg) {
2+
if (arg && typeof arg._toFilterArg === 'function')
3+
return arg._toFilterArg(context);
24
if (column.encode.safe)
35
return column.encode.safe(context, arg);
46
else
57
return column.encode(context, arg);
68
}
79

8-
module.exports = encodeFilterArg;
10+
module.exports = encodeFilterArg;

src/table/column/newColumn.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,24 @@ const _extractAlias = require('./extractAlias');
99
const quote = require('../../table/quote');
1010
const aggregate = require('./columnAggregate');
1111
const aggregateGroup = require('./columnAggregateGroup');
12+
const newParameterized = require('../query/newParameterized');
1213

1314
module.exports = function(table, name) {
1415
var c = {};
1516
var extractAlias = _extractAlias.bind(null, table);
1617
c._dbName = name;
1718
c.alias = name;
1819
table._aliases.add(name);
20+
Object.defineProperty(c, '_table', {
21+
value: table,
22+
enumerable: false,
23+
writable: false
24+
});
25+
Object.defineProperty(c, '_toFilterArg', {
26+
value: toFilterArg,
27+
enumerable: false,
28+
writable: false
29+
});
1930

2031
c.dbNull = null;
2132
table._columns.push(c);
@@ -101,5 +112,11 @@ module.exports = function(table, name) {
101112
};
102113
}
103114

115+
function toFilterArg(context) {
116+
const tableAlias = quote(context, table._rootAlias || table._dbName);
117+
const columnName = quote(context, c._dbName);
118+
return newParameterized(`${tableAlias}.${columnName}`);
119+
}
120+
104121
return c;
105122
};
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
const quote = require('../../quote');
22
var newBoolean = require('../newBoolean');
33
var nullOperator = ' is ';
4+
var encodeFilterArg = require('../encodeFilterArg');
5+
var newLikeColumnArg = require('./newLikeColumnArg');
46

5-
function endsWithCore(context, operator, column,arg,alias) {
7+
function containsCore(context, operator, column,arg,alias) {
68
alias = quote(context, alias);
79
operator = ' ' + operator + ' ';
8-
var encoded = column.encode(context, arg);
10+
var encoded = encodeFilterArg(context, column, arg);
911
if (encoded.sql() == 'null')
1012
operator = nullOperator;
13+
else if (arg && typeof arg._toFilterArg === 'function')
14+
encoded = newLikeColumnArg(context, column, encoded, '%', '%');
1115
else
1216
encoded = column.encode(context, '%' + arg + '%');
1317
var firstPart = alias + '.' + quote(context, column._dbName) + operator;
1418
var filter = encoded.prepend(firstPart);
1519
return newBoolean(filter);
1620
}
1721

18-
module.exports = endsWithCore;
22+
module.exports = containsCore;
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
const quote = require('../../quote');
22
var newBoolean = require('../newBoolean');
33
var nullOperator = ' is ';
4+
var encodeFilterArg = require('../encodeFilterArg');
5+
var newLikeColumnArg = require('./newLikeColumnArg');
46

57
function endsWithCore(context, operator, column,arg,alias) {
68
alias = quote(context, alias);
79
operator = ' ' + operator + ' ';
8-
var encoded = column.encode(context, arg);
10+
var encoded = encodeFilterArg(context, column, arg);
911
if (encoded.sql() == 'null')
1012
operator = nullOperator;
13+
else if (arg && typeof arg._toFilterArg === 'function')
14+
encoded = newLikeColumnArg(context, column, encoded, '%', null);
1115
else
1216
encoded = column.encode(context, '%' + arg);
1317
var firstPart = alias + '.' + quote(context, column._dbName) + operator;
1418
var filter = encoded.prepend(firstPart);
1519
return newBoolean(filter);
1620
}
1721

18-
module.exports = endsWithCore;
22+
module.exports = endsWithCore;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
var getSessionSingleton = require('../../getSessionSingleton');
2+
var newParameterized = require('../../query/newParameterized');
3+
4+
function newLikeColumnArg(context, column, encodedArg, prefix, suffix) {
5+
var encodedPrefix = prefix ? column.encode(context, prefix) : null;
6+
var encodedSuffix = suffix ? column.encode(context, suffix) : null;
7+
var engine = getSessionSingleton(context, 'engine');
8+
9+
if (engine === 'mysql')
10+
return concatWithFunction(encodedPrefix, encodedArg, encodedSuffix);
11+
if (engine === 'mssql' || engine === 'mssqlNative')
12+
return concatWithOperator('+', encodedPrefix, encodedArg, encodedSuffix);
13+
return concatWithOperator('||', encodedPrefix, encodedArg, encodedSuffix);
14+
}
15+
16+
function concatWithFunction(prefix, value, suffix) {
17+
var args = [prefix, value, suffix].filter(Boolean);
18+
var sql = 'CONCAT(' + args.map(x => x.sql()).join(',') + ')';
19+
var parameters = [];
20+
for (var i = 0; i < args.length; i++)
21+
parameters = parameters.concat(args[i].parameters);
22+
return newParameterized(sql, parameters);
23+
}
24+
25+
function concatWithOperator(operator, prefix, value, suffix) {
26+
var args = [prefix, value, suffix].filter(Boolean);
27+
var sql = '';
28+
var parameters = [];
29+
for (var i = 0; i < args.length; i++) {
30+
if (i > 0)
31+
sql += ' ' + operator + ' ';
32+
sql += args[i].sql();
33+
parameters = parameters.concat(args[i].parameters);
34+
}
35+
return newParameterized(sql, parameters);
36+
}
37+
38+
module.exports = newLikeColumnArg;

0 commit comments

Comments
 (0)