Skip to content

Commit 81c3990

Browse files
authored
Story - Related data paging (#8)
1 parent 4d76aa8 commit 81c3990

File tree

13 files changed

+831
-199
lines changed

13 files changed

+831
-199
lines changed

related-data-paging.sql

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
select * from "user"
2+
3+
select * from "user_group"
4+
5+
select * from user_to_user_group
6+
7+
select * from "image"
8+
9+
select * from "image" where creator_user_id = 2
10+
11+
------------------------------------------------
12+
-- non-many-to-many, non-range-constrained
13+
------------------------------------------------
14+
select
15+
-- Root data node columns
16+
"1".uuid "1.uuid", "1".date_created "1.dateCreated", "1".file_name "1.fileName",
17+
-- Root data node linked field column
18+
"1".creator_user_id "1.creatorUserId",
19+
-- To-one related data columns
20+
"2".id "2.id", "2".name "2.name"
21+
-- Root from
22+
from "image" "1"
23+
-- To-one related data left joins
24+
left join "user" "2" on "2".id = "1"."creator_user_id"
25+
-- Linked field value where clause
26+
where "1".creator_user_id = any (values (1),(2),(3))
27+
-- Root data node query SQL
28+
and "1".date_deleted is null
29+
order by "1".id
30+
31+
------------------------------------------------
32+
-- non-many-to-many, range-constrained
33+
------------------------------------------------
34+
select
35+
"_0".id "_0.id", "_0".name "_0.name",
36+
-- Root data node columns
37+
"1"."uuid" "1.uuid", "1"."file_name" "1.fileName",
38+
-- Root data node linked field column
39+
"1"."creator_user_id" "1.creatorUserId",
40+
-- To-one related data columns
41+
"2".id "2.id", "2".name "2.name"
42+
-- Root from
43+
from "user" "_0"
44+
-- Range-constraint part of root "from"
45+
join lateral (
46+
select
47+
"uuid", "date_created", "file_name", "creator_user_id"
48+
from "image" "1"
49+
where "1".creator_user_id = "_0"."id"
50+
-- Root data node query SQL
51+
and "1"."date_deleted" is null
52+
order by "1".id
53+
limit 2
54+
offset 2
55+
) as "1" on "1"."creator_user_id" = "_0"."id"
56+
-- To-one related data left joins
57+
left join "user" "2" on "2".id = "1"."creator_user_id"
58+
-- Linked field value where clause
59+
where "_0"."id" = any (values (1),(2),(3))
60+
61+
------------------------------------------------
62+
-- many-to-many, non-range-constrained
63+
------------------------------------------------
64+
select
65+
-- Root data node columns
66+
"1".id "1.id", "1".uuid "1.uuid", "1".name "1.name", "1".date_created "1.dateCreated",
67+
-- Root data node join table columns
68+
"u2ug".user_id "u2ug.user_id", "u2ug".user_group_id "u2ug.user_group_id",
69+
-- To-one related data columns
70+
"2".id "2.id", "2".name "2.name"
71+
-- Root from
72+
from "user_to_user_group" "u2ug"
73+
join "user_group" "1" on "1".id = "u2ug".user_group_id
74+
-- To-one related data left joins
75+
left join "user" "2" on "2".id = "1"."id"
76+
-- Linked field value where clause
77+
where "u2ug".user_id = any (values (1),(2),(3))
78+
-- Root data node query SQL
79+
and "1".date_deleted is null
80+
order by "1".id
81+
82+
------------------------------------------------
83+
-- many-to-many, range-constrained
84+
------------------------------------------------
85+
select
86+
-- Root data node columns
87+
"1"."id" "1.id", "1"."uuid" "1.uuid", "1"."name" "1.name", "1"."date_created" "1.dateCreated",
88+
-- Root data node join table columns
89+
"1"."u2ug.user_id" "u2ug.user_id", "1"."u2ug.user_group_id" "u2ug.user_group_id",
90+
-- To-one related data columns
91+
"2".id "2.id", "2".name "2.name"
92+
-- Root from
93+
from "user" "_0"
94+
-- Range-constraint part of root "from"
95+
join lateral (
96+
select
97+
"1".id "id", "1".uuid "uuid", "1".name "name", "1".date_created "date_created",
98+
"u2ug".user_id "u2ug.user_id", "u2ug".user_group_id "u2ug.user_group_id"
99+
from "user_to_user_group" "u2ug"
100+
join "user_group" "1" on "1".id = "u2ug"."user_group_id"
101+
where "u2ug"."user_id" = "_0"."id"
102+
-- Root data node query SQL
103+
and "1".date_deleted is null
104+
order by "1".id
105+
limit 2
106+
offset 2
107+
) as "1" on "1"."u2ug.user_id" = "_0"."id"
108+
-- To-one related data left joins
109+
left join "user" "2" on "2".id = "1"."id"
110+
-- Linked field value where clause
111+
where "_0"."id" = any (values (1),(2),(3))
112+
113+
114+
select
115+
"1".id "1.id", "1".uuid "1.uuid", "1".date_created "1.dateCreated", "1".date_deleted "1.dateDeleted", "1".name "1.name", "1".description "1.description", "1".image_id "1.imageId",
116+
"1"."user_to_user_group.user_id" "user_to_user_group.user_id", "1"."user_to_user_group.user_group_id" "user_to_user_group.user_group_id"
117+
from "user" "_0"
118+
join lateral (
119+
select
120+
"1"."id" "id", "1"."uuid" "uuid", "1"."date_created" "date_created", "1"."date_deleted" "date_deleted", "1"."name" "name", "1"."description" "description", "1"."image_id" "image_id",
121+
"user_to_user_group".user_id "user_to_user_group.user_id", "user_to_user_group".user_group_id "user_to_user_group.user_group_id"
122+
from user_to_user_group "user_to_user_group"
123+
join "user_group" "1" on "1".id = "user_to_user_group"."user_group_id"
124+
where "user_to_user_group"."user_id" = "_0"."id"
125+
limit 1 offset 0
126+
) as "1" on "1"."user_to_user_group.user_id" = "_0"."id"
127+
where "_0"."id" = any (values (1),(2),(3))

src/dataFormat/index.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export const createDataFormat = <T extends DataFormatDeclaration>(
114114
name: dataFormatDeclaration.name,
115115
capitalizedName: capitalize(dataFormatDeclaration.name) as any,
116116
pluralizedName,
117-
capitalizedPluralizedName: capitalize(pluralizedName),
117+
capitalizedPluralizedName: capitalize(pluralizedName) as any,
118118
declaration: dataFormatDeclaration,
119119
fields,
120120
fieldNames: fieldNamesDict,
@@ -131,9 +131,8 @@ export const createDataFormat = <T extends DataFormatDeclaration>(
131131
if (fname === COMMON_FIELDS.dateDeleted.name)
132132
return
133133

134-
// TODO: Although fname is always going to be a key in "fields", TS doesn't see it.
135-
// @ts-ignore
136-
record[fname] = createSampleData(fields[fname])
134+
// Although fname is always going to be a key in "fields", TS doesn't see it.
135+
(record as any)[fname] = createSampleData(fields[fname])
137136
})
138137
return record
139138
},

src/dataFormat/types.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,78 +6,78 @@ export enum DataType {
66
/**
77
* Any numeric value, i.e. integer, serial, real.
88
*/
9-
NUMBER,
9+
NUMBER = 'num',
1010
/**
1111
* Any string value, i.e. varying length, fixed length, etc.
1212
*/
13-
STRING,
13+
STRING = 'str',
1414
/**
1515
* Any boolean value
1616
*/
17-
BOOLEAN,
17+
BOOLEAN = 'bool',
1818
/**
1919
* Any epoch value, i.e. time, date, date-time, date-time with timezone.
2020
*/
21-
DATE,
21+
DATE = 'date',
2222
/**
2323
* Any object or array value.
2424
*/
25-
JSON
25+
JSON = 'json'
2626
}
2727

2828
export enum NumberDataSubType {
29-
INTEGER,
29+
INTEGER = 'int',
3030
/**
3131
* A serial integer value. This is likely to be for the unique primary key field of the data format.
3232
*/
33-
SERIAL,
34-
REAL
33+
SERIAL = 'ser',
34+
REAL = 'real'
3535
}
3636

3737
export enum StringDataSubType {
3838
/**
3939
* A string value that is a UUID V4, i.e. a 36 character randomly-generated string.
4040
*/
41-
UUID_V4,
41+
UUID_V4 = 'uuidv4',
4242
/**
4343
* A fixed-length string.
4444
*/
45-
FIXED_LENGTH,
45+
FIXED_LENGTH = 'fixed',
4646
/**
4747
* A varying-length string.
4848
*/
49-
VARYING_LENGTH,
49+
VARYING_LENGTH = 'var',
5050
/**
5151
* A string that, underlying, is some kind of enumeration.
5252
*/
53-
STRING_ENUM,
53+
STRING_ENUM = 'enum',
5454
}
5555

5656
export enum BooleanDataSubType {
57-
TRUE_FALSE,
57+
TRUE_FALSE = 'truefalse',
5858
}
5959

6060
export enum DateDataSubType {
61-
DATE,
62-
TIME,
63-
DATE_TIME,
64-
DATE_TIME_WITH_TIMEZONE
61+
DATE = 'date',
62+
TIME = 'time',
63+
DATE_TIME = 'datetime',
64+
DATE_TIME_WITH_TIMEZONE = 'datetime_timezone'
6565
}
6666

6767
export enum ThreeStepNumberSize {
68-
SMALL,
69-
REGULAR,
70-
LARGE,
68+
SMALL = 'small',
69+
REGULAR = 'reg',
70+
LARGE = 'large',
7171
}
7272

7373
export enum TwoStepNumberSize {
74-
REGULAR,
75-
LARGE,
74+
REGULAR = 'reg',
75+
LARGE = 'large',
7676
}
7777

7878
export enum JsonDataSubType {
79-
ARRAY,
80-
OBJECT
79+
ARRAY = 'arr',
80+
OBJECT = 'obj'
8181
}
8282

8383
export type DataTypeToSubType = {

src/examples/realDbTest/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,19 @@ const init = async () => {
247247
}), 'query')
248248
fs.writeFileSync(path.resolve(outputDir, 'user-user-groups-query.json'), JSON.stringify(result2.result, null, 2))
249249

250+
const result3 = await timedFn(() => stores.user.getMultiple({
251+
fields: [],
252+
relations: {
253+
userGroups: {
254+
query: {
255+
page: 1,
256+
pageSize: 1,
257+
},
258+
},
259+
},
260+
}), 'query')
261+
fs.writeFileSync(path.resolve(outputDir, 'user-user-groups-query-range-constraint.json'), JSON.stringify(result3.result, null, 2))
262+
250263
const dtList: number[] = []
251264
await repeatTimedFn(() => getResult(stores), 'query', 10, dtList)
252265
const avgDt = dtList.reduce((acc, dt) => acc + dt, 0) / dtList.length

src/helpers/array.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,13 @@ export const removeDuplicates = <T>(array: T[]): T[] => {
99
acc.indexOf(item) === -1 ? acc.concat(item) : acc
1010
), [])
1111
}
12+
13+
export const removeNullAndUndefinedValues = <T>(array: T[]): T[] => {
14+
if (array == null)
15+
return null
16+
17+
if (array.length === 0)
18+
return []
19+
20+
return array.filter(item => item != null)
21+
}

src/helpers/string.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,24 @@ export const capitalize = <T extends string>(s: T): Capitalize<T> => (
5353
`${s.charAt(0).toUpperCase()}${s.slice(1)}` as Capitalize<T>
5454
)
5555

56+
/**
57+
* Removes null, undefined, and empty strings from `arr`.
58+
*/
5659
export const filterForNotNullAndEmpty = (arr: string[]) => (
5760
arr.filter(s => s != null && s.length > 0)
5861
)
5962

63+
/**
64+
* Concatenates the list of strings - `arr`, if and only if `arr` is defined and has
65+
* at least one entry.
66+
*/
6067
export const joinIfhasEntries = (arr: string[], joinStr: string) => (
6168
arr != null && arr.length > 0 ? arr.join(joinStr) : null
6269
)
6370

71+
/**
72+
* Concatenates `prefix` and `suffix` together if and only if both of them are defined.
73+
*/
6474
export const concatIfNotNullAndEmpty = (prefix: string, suffix: string) => {
6575
if (prefix != null && suffix != null)
6676
return prefix.concat(suffix)

src/relations/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export enum RelationType {
1313
*
1414
* Foreign field: unique
1515
*/
16-
ONE_TO_ONE,
16+
ONE_TO_ONE = 'one_to_one',
1717
/**
1818
* One item of this format relates to multiple items on another format.
1919
*
@@ -25,7 +25,7 @@ export enum RelationType {
2525
*
2626
* Foreign field: not unique
2727
*/
28-
ONE_TO_MANY,
28+
ONE_TO_MANY = 'one_to_many',
2929
/**
3030
* Multiple items of this format relates to multiple items of another format.
3131
*
@@ -37,7 +37,7 @@ export enum RelationType {
3737
* Use `createJoinTables` to create them once the relations have been loaded into
3838
* the TsPgOrm instance.
3939
*/
40-
MANY_TO_MANY,
40+
MANY_TO_MANY = 'many_to_many',
4141
}
4242

4343
type ExtractAvailableFieldRefs<T extends DataFormatDeclarations> = ExpandRecursively<ValuesUnionFromDict<{

src/store/get/dataNodes.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { removeDuplicates } from '../../helpers/array'
33
import { toDict } from '../../helpers/dict'
44
import { RelationsDict, RelationType, Relation, RelationDeclarations } from '../../relations/types'
55
import { AnyGetFunctionOptions, GetFunctionOptions } from '../types/get'
6-
import { RelatedDataInfoDict, DataNodes, UnresolvedDataNodes, FieldsInfo, DataNode } from './types'
6+
import { RelatedDataInfoDict, DataNodes, UnresolvedDataNodes, FieldsInfo, DataNode, PluralDataNode, NonPluralDataNode } from './types'
77

88
/**
99
* Determines all of the related data properties of the given `dataFormat`
@@ -176,12 +176,12 @@ const createFieldsInfo = (dataNode: DataNode): FieldsInfo => {
176176
const parentJoinTableColumnName = isFieldRefFieldRef1
177177
? dataNode.relation.sql.joinTableFieldRef2ColumnName
178178
: dataNode.relation.sql.joinTableFieldRef1ColumnName
179-
joinTableParentFullyQualifiedColumnName = `"${joinTableAlias}".${parentJoinTableColumnName}`
179+
joinTableParentFullyQualifiedColumnName = `"${joinTableAlias}"."${parentJoinTableColumnName}"`
180180
joinTableParentColumnNameAlias = `${joinTableAlias}.${parentJoinTableColumnName}`
181181
const joinTableColumnName = isFieldRefFieldRef1
182182
? dataNode.relation.sql.joinTableFieldRef1ColumnName
183183
: dataNode.relation.sql.joinTableFieldRef2ColumnName
184-
joinTableFullyQualifiedColumnName = `"${joinTableAlias}".${joinTableColumnName}`
184+
joinTableFullyQualifiedColumnName = `"${joinTableAlias}"."${joinTableColumnName}"`
185185
}
186186

187187
return {
@@ -211,6 +211,7 @@ const resolveDataNodes = (unresolvedDataNodes: UnresolvedDataNodes): DataNodes =
211211
relatedDataPropName: node.relatedDataPropName,
212212
relation: node.relation,
213213
tableAlias: `"${node.id}"`,
214+
unquotedTableAlias: node.id.toString(),
214215
createColumnsSqlSegments: () => dataNode.fieldsInfo.fieldsToSelectFor.map(fName => (
215216
`${dataNode.fieldsInfo.fieldToFullyQualifiedColumnName[fName]} "${dataNode.fieldsInfo.fieldToColumnNameAlias[fName]}"`
216217
)),
@@ -260,12 +261,14 @@ export const toDataNodes = <
260261
unresolvedDataNodes,
261262
{ id: 0 },
262263
)
263-
/* Resolve each data node in the dict, which means, amongst some other changes, means
264-
* to convert all of the child and parent data node id links into references to other
264+
/* Resolve each data node in the dict, which means, amongst some other changes,
265+
* converting all of the child and parent data node id links into references to other
265266
* data nodes. This will make things much easier later on in the query plan framework,
266267
* such as making it so that we don't have to carry around the whole data nodes dict
267268
* all the time (because each resovled data node will have direct references to
268269
* surrounding data nodes).
269270
*/
270271
return resolveDataNodes(unresolvedDataNodes)
271272
}
273+
274+
export const isDataNodePlural = (dataNode: PluralDataNode | NonPluralDataNode): dataNode is PluralDataNode => dataNode.isPlural

0 commit comments

Comments
 (0)