generated from johngeorgewright/ts-module
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from johngeorgewright/pql
feat: add pql function
- Loading branch information
Showing
9 changed files
with
304 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export function castArray<T>(x: undefined | T | T[]): T[] { | ||
if (x === undefined) return [] | ||
if (Array.isArray(x)) return x | ||
return [x] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function entries<X extends Object>(x: X) { | ||
return Object.entries(x) as [keyof X, X[keyof X]][] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { PickOfValue } from '../types/PickOfValue' | ||
import { entries } from '../lang/Object' | ||
|
||
type Contionable = string | boolean | number | ||
|
||
type Condition<T> = { | ||
[K in keyof PickOfValue<T, Contionable>]?: | ||
| Is<Exclude<T[K], undefined>> | ||
| IsNot< | ||
Exclude<T[K], undefined> extends string | ||
? Exclude<T[K], undefined> | IsLike | ||
: Exclude<T[K], undefined> | ||
> | ||
| IsLike | ||
} | ||
|
||
type Is<T> = null | (T extends boolean ? T : T | T[]) | ||
|
||
interface IsNot<T> { | ||
__type__: 'not' | ||
value: Is<T> | ||
} | ||
|
||
type IsLikeString = `${string}%${string}` | ||
|
||
interface IsLike { | ||
__type__: 'like' | ||
value: IsLikeString | ||
} | ||
|
||
export function not<T>(value: Is<T>): IsNot<T> { | ||
return { __type__: 'not', value } | ||
} | ||
|
||
export function like(value: IsLikeString): IsLike { | ||
return { __type__: 'like', value } | ||
} | ||
|
||
export function pql<T>({ | ||
limit, | ||
offset, | ||
where: conditions, | ||
}: { | ||
limit?: number | ||
offset?: number | ||
where?: Condition<T> | Condition<T>[] | ||
}): string { | ||
let pql = '' | ||
|
||
if (conditions) { | ||
pql += `WHERE ${ | ||
Array.isArray(conditions) | ||
? `(${conditions.map(where).join(') OR (')})` | ||
: where(conditions) | ||
}` | ||
} | ||
|
||
if (limit) { | ||
pql += ` LIMIT ${limit}` | ||
} | ||
|
||
if (offset) { | ||
pql += ` OFFSET ${offset}` | ||
} | ||
|
||
return pql | ||
|
||
function where(condition: Condition<T>) { | ||
return entries(condition) | ||
.map(([key, value]) => formatCondition(key, value)) | ||
.join(' AND ') | ||
} | ||
} | ||
|
||
function formatCondition(key: keyof any, value: unknown) { | ||
const not = isNot(value) | ||
const x = not ? value.value : value | ||
|
||
return Array.isArray(x) | ||
? `${not ? 'NOT ' : ''}${key.toString()} IN (${x.map(formatValue)})` | ||
: isLike(x) | ||
? `${not ? 'NOT ' : ''}${key.toString()} LIKE ${formatValue(x)}` | ||
: `${key.toString()} ${not ? '!=' : '='} ${formatValue(x)}` | ||
} | ||
|
||
function formatValue(value: Is<any>): string { | ||
switch (typeof value) { | ||
case 'boolean': | ||
return value ? 'TRUE' : 'FALSE' | ||
case 'number': | ||
return value.toString() | ||
default: | ||
return isNot(value) || isLike(value) | ||
? formatValue(value.value) | ||
: `'${value.replace("'", "\\'")}'` | ||
} | ||
} | ||
|
||
function isNot<T>(x: unknown): x is IsNot<T> { | ||
return ( | ||
typeof x === 'object' && | ||
x !== null && | ||
'__type__' in x && | ||
x.__type__ === 'not' && | ||
'value' in x | ||
) | ||
} | ||
|
||
function isLike(x: unknown): x is IsLike { | ||
return ( | ||
typeof x === 'object' && | ||
x !== null && | ||
'__type__' in x && | ||
x.__type__ === 'like' && | ||
'value' in x && | ||
typeof x.value === 'string' | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export type PickOfValue<T, V> = { | ||
[K in keyof T as Extract<T[K], V> extends never ? never : K]: T[K] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
export type ResultItem<R extends Response<unknown>> = R extends Response< | ||
infer T | ||
> | ||
? T | ||
: never | ||
|
||
interface Response<T> { | ||
rval?: Rval<T> | ||
} | ||
|
||
interface Rval<T> { | ||
results?: T[] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { like, not, pql } from '../src/query/pql' | ||
import { Creatives } from '../src/service/v202308/creativeservice' | ||
import { LineItems } from '../src/service/v202308/lineitemservice' | ||
|
||
test('positive', () => { | ||
expect( | ||
pql<Creatives>({ | ||
where: { | ||
name: 'foo', | ||
}, | ||
}), | ||
).toBe("WHERE name = 'foo'") | ||
|
||
expect( | ||
pql<Creatives>({ | ||
where: { | ||
name: "foo's", | ||
}, | ||
}), | ||
).toBe("WHERE name = 'foo\\'s'") | ||
|
||
expect( | ||
pql<Creatives>({ | ||
where: { | ||
id: 1234, | ||
}, | ||
}), | ||
).toBe('WHERE id = 1234') | ||
|
||
expect( | ||
pql<LineItems>({ | ||
where: { | ||
allowOverbook: true, | ||
}, | ||
}), | ||
).toBe('WHERE allowOverbook = TRUE') | ||
|
||
expect( | ||
pql<LineItems>({ | ||
where: { | ||
allowOverbook: false, | ||
}, | ||
}), | ||
).toBe('WHERE allowOverbook = FALSE') | ||
|
||
expect( | ||
pql<Creatives>({ | ||
where: { | ||
name: ['foo', 'bar'], | ||
}, | ||
}), | ||
).toBe("WHERE name IN ('foo','bar')") | ||
|
||
expect( | ||
pql<Creatives>({ | ||
where: { | ||
name: like('foo %'), | ||
}, | ||
}), | ||
).toBe("WHERE name LIKE 'foo %'") | ||
}) | ||
|
||
test('negative', () => { | ||
expect( | ||
pql<Creatives>({ | ||
where: { | ||
name: not('foo'), | ||
}, | ||
}), | ||
).toBe("WHERE name != 'foo'") | ||
|
||
expect( | ||
pql<Creatives>({ | ||
where: { | ||
id: not(1234), | ||
}, | ||
}), | ||
).toBe('WHERE id != 1234') | ||
|
||
expect( | ||
pql<Creatives>({ | ||
where: { | ||
name: not(['foo', 'bar']), | ||
}, | ||
}), | ||
).toBe("WHERE NOT name IN ('foo','bar')") | ||
|
||
expect( | ||
pql<Creatives>({ | ||
where: { | ||
name: not(like('foo %')), | ||
}, | ||
}), | ||
).toBe("WHERE NOT name LIKE 'foo %'") | ||
}) | ||
|
||
test('ands', () => { | ||
expect( | ||
pql<Creatives>({ | ||
where: { | ||
name: 'foo', | ||
previewUrl: 'bar', | ||
}, | ||
}), | ||
).toBe("WHERE name = 'foo' AND previewUrl = 'bar'") | ||
}) | ||
|
||
test('ors', () => { | ||
expect( | ||
pql<Creatives>({ | ||
where: [ | ||
{ | ||
name: 'foo', | ||
previewUrl: 'bar', | ||
}, | ||
{ | ||
id: 123, | ||
advertiserId: 333, | ||
}, | ||
], | ||
}), | ||
).toBe( | ||
"WHERE (name = 'foo' AND previewUrl = 'bar') OR (id = 123 AND advertiserId = 333)", | ||
) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters