@graffy/pg 0.16.4-alpha.1 → 0.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +6 -0
- package/index.cjs +74 -42
- package/index.mjs +74 -42
- package/package.json +2 -2
- package/types/Db.d.ts +1 -1
- package/types/index.d.ts +11 -6
package/Readme.md
CHANGED
|
@@ -20,6 +20,9 @@ store.use(path, pg(options));
|
|
|
20
20
|
- `table`, the name of the table. If not provided, the last segment of the `path` is used. This table must exist.
|
|
21
21
|
- `idCol`: the name of the column to use as ID. Defaults to `id`. This column must exist and be the primary key or have a unique constraint.
|
|
22
22
|
- `verCol`: the name of the column to store the Graffy version number. This column must exist, and must have a `DEFAULT` SQL expression defined - this expression is evaluated to calculate the version number. Graffy versions must monotonically increase, so this expression is typically based on `CURRENT_TIMESTAMP`.
|
|
23
|
+
- `joins`: other tables that have foreign keys referencing the ID column in this table, where we want to filter this table using columns of that other table via a "join". The value is a map of join names to join options.
|
|
24
|
+
- join names are the names used to refer to joined tables in filter expressions. Typically, these are the names of those tables.
|
|
25
|
+
- join options are objects with optional properties `table`, `idCol`, `refCol` and `verCol`. `refCol` is the name of the column in the join table that references this one.
|
|
23
26
|
- `connection`: a [pg](https://github.com/brianc/node-postgres) Client or Pool object (recommended), or the arguments for constructing a new Pool object. Optional.
|
|
24
27
|
|
|
25
28
|
### Database connection
|
|
@@ -89,6 +92,9 @@ Tags must contain both *foo* and *bar*. Note that the array of conditions here d
|
|
|
89
92
|
2. `{ tags: { $ctd: ['foo', 'bar', 'baz'] } }` becomes `tags <@ '{"foo","bar","baz"}'`.
|
|
90
93
|
Every tag must be one of *foo*, *bar* or *baz*.
|
|
91
94
|
|
|
95
|
+
#### Joins
|
|
96
|
+
1. `joinName: { ...expression }`,
|
|
97
|
+
|
|
92
98
|
#### Notes
|
|
93
99
|
|
|
94
100
|
1. We drop several MongoDB operators while retaining the capability:
|
package/index.cjs
CHANGED
|
@@ -126,9 +126,7 @@ function construct(node, prop, op) {
|
|
|
126
126
|
return construct(val, prop, key);
|
|
127
127
|
}
|
|
128
128
|
if (prop) {
|
|
129
|
-
|
|
130
|
-
return construct(val, prop + key);
|
|
131
|
-
throw Error(`pgast.unexpected_prop: ${key}`);
|
|
129
|
+
return ["$sub", prop, construct({ [key]: val })];
|
|
132
130
|
}
|
|
133
131
|
return construct(val, key);
|
|
134
132
|
})
|
|
@@ -140,6 +138,8 @@ function simplify(node) {
|
|
|
140
138
|
node[1] = node[1].map((subnode) => simplify(subnode));
|
|
141
139
|
} else if (op === "$not") {
|
|
142
140
|
node[1] = simplify(node[1]);
|
|
141
|
+
} else if (op === "$sub") {
|
|
142
|
+
node[2] = simplify(node[2]);
|
|
143
143
|
}
|
|
144
144
|
if (op === "$and") {
|
|
145
145
|
if (!node[1].length)
|
|
@@ -385,36 +385,48 @@ function getBinarySql(lhs, type, op, value, textLhs) {
|
|
|
385
385
|
return sql`${lhs} ${sqlOp} ${cubeLiteralSql(value)}`;
|
|
386
386
|
return sql`${lhs} ${sqlOp} ${value}`;
|
|
387
387
|
}
|
|
388
|
-
function
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
388
|
+
function getNodeSql(ast, options) {
|
|
389
|
+
if (typeof ast === "boolean")
|
|
390
|
+
return ast;
|
|
391
|
+
const op = ast[0];
|
|
392
|
+
if (op === "$and" || op === "$or") {
|
|
393
|
+
return sql`(${join(
|
|
394
|
+
ast[1].map((node) => getNodeSql(node, options)),
|
|
395
|
+
`) ${opSql[op]} (`
|
|
396
|
+
)})`;
|
|
397
|
+
} else if (op === "$not") {
|
|
398
|
+
return sql`${opSql[op]} (${getNodeSql(ast[1], options)})`;
|
|
399
|
+
}
|
|
400
|
+
if (op === "$sub") {
|
|
401
|
+
const joinName = ast[1];
|
|
402
|
+
if (!options.joins[joinName])
|
|
403
|
+
throw Error(`pg.no_join ${joinName}`);
|
|
404
|
+
const { idCol, schema } = options;
|
|
405
|
+
const joinOptions = options.joins[joinName];
|
|
406
|
+
const { table: joinTable, refCol } = options.joins[joinName];
|
|
407
|
+
return sql`"${raw(idCol)}" IN (SELECT "${raw(refCol)}"::${raw(
|
|
408
|
+
schema.types[idCol]
|
|
409
|
+
)} FROM "${raw(joinTable)}" WHERE ${getNodeSql(ast[2], joinOptions)})`;
|
|
410
|
+
}
|
|
411
|
+
const [prefix, ...suffix] = ast[1].split(".");
|
|
412
|
+
const { types: types2 = {} } = options.schema;
|
|
413
|
+
if (!types2[prefix])
|
|
414
|
+
throw Error(`pg.no_column ${prefix}`);
|
|
415
|
+
if (types2[prefix] === "jsonb") {
|
|
416
|
+
const [lhs, textLhs] = suffix.length ? [
|
|
417
|
+
sql`"${raw(prefix)}" #> ${suffix}`,
|
|
418
|
+
sql`"${raw(prefix)}" #>> ${suffix}`
|
|
419
|
+
] : [sql`"${raw(prefix)}"`, sql`"${raw(prefix)}" #>> '{}'`];
|
|
420
|
+
return getBinarySql(lhs, "jsonb", op, ast[2], textLhs);
|
|
421
|
+
} else {
|
|
422
|
+
if (suffix.length)
|
|
423
|
+
throw Error(`pg.lookup_not_jsonb ${prefix}`);
|
|
424
|
+
return getBinarySql(sql`"${raw(prefix)}"`, types2[prefix], op, ast[2]);
|
|
416
425
|
}
|
|
417
|
-
|
|
426
|
+
}
|
|
427
|
+
function getSql(filter, options) {
|
|
428
|
+
const ast = getAst(filter);
|
|
429
|
+
return getNodeSql(ast, options);
|
|
418
430
|
}
|
|
419
431
|
const getIdMeta = ({ idCol, verDefault }) => sql`"${raw(idCol)}" AS "$key", ${raw(verDefault)} AS "$ver"`;
|
|
420
432
|
const getArgMeta = (key, { prefix, idCol, verDefault }) => sql`
|
|
@@ -658,10 +670,10 @@ class Db {
|
|
|
658
670
|
}
|
|
659
671
|
return res.rows[0];
|
|
660
672
|
}
|
|
661
|
-
async ensureSchema(tableOptions) {
|
|
673
|
+
async ensureSchema(tableOptions, typeOids) {
|
|
662
674
|
if (tableOptions.schema)
|
|
663
675
|
return;
|
|
664
|
-
const { table, verCol } = tableOptions;
|
|
676
|
+
const { table, verCol, joins } = tableOptions;
|
|
665
677
|
const tableSchema = (await this.query(sql`
|
|
666
678
|
SELECT table_schema
|
|
667
679
|
FROM information_schema.tables
|
|
@@ -676,7 +688,7 @@ class Db {
|
|
|
676
688
|
table_schema = ${tableSchema}`)).rows[0].column_types;
|
|
677
689
|
if (!types2)
|
|
678
690
|
throw Error(`pg.missing_table ${table}`);
|
|
679
|
-
|
|
691
|
+
typeOids = typeOids || (await this.query(sql`
|
|
680
692
|
SELECT jsonb_object_agg(typname, oid) AS type_oids
|
|
681
693
|
FROM pg_type
|
|
682
694
|
WHERE typname = 'cube'`)).rows[0].type_oids;
|
|
@@ -690,6 +702,11 @@ class Db {
|
|
|
690
702
|
if (!verDefault) {
|
|
691
703
|
throw Error(`pg.verCol_without_default ${verCol}`);
|
|
692
704
|
}
|
|
705
|
+
await Promise.all(
|
|
706
|
+
Object.values(joins).map(
|
|
707
|
+
(joinOptions) => this.ensureSchema(joinOptions, typeOids)
|
|
708
|
+
)
|
|
709
|
+
);
|
|
693
710
|
log("ensureSchema", types2);
|
|
694
711
|
tableOptions.schema = { types: types2, typeOids };
|
|
695
712
|
tableOptions.verDefault = verDefault;
|
|
@@ -780,18 +797,33 @@ class Db {
|
|
|
780
797
|
return result;
|
|
781
798
|
}
|
|
782
799
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
const tableOpts = {
|
|
788
|
-
prefix,
|
|
789
|
-
table: table || prefix[prefix.length - 1] || "default",
|
|
800
|
+
function getTableOpts(name, { table, idCol, verCol, joins, schema, verDefault } = {}, parentName = null) {
|
|
801
|
+
const tableName = table || name;
|
|
802
|
+
return {
|
|
803
|
+
table: table || name,
|
|
790
804
|
idCol: idCol || "id",
|
|
791
805
|
verCol: verCol || "updatedAt",
|
|
806
|
+
joins: Object.fromEntries(
|
|
807
|
+
Object.entries(joins || {}).map(
|
|
808
|
+
([joinName, { refCol = parentName, ...joinOptions }]) => [
|
|
809
|
+
joinName,
|
|
810
|
+
{
|
|
811
|
+
refCol,
|
|
812
|
+
...getTableOpts(joinName, joinOptions, tableName)
|
|
813
|
+
}
|
|
814
|
+
]
|
|
815
|
+
)
|
|
816
|
+
),
|
|
792
817
|
schema,
|
|
793
818
|
verDefault
|
|
794
819
|
};
|
|
820
|
+
}
|
|
821
|
+
const pg = ({ connection, ...rawOptions }) => (store) => {
|
|
822
|
+
store.on("read", read);
|
|
823
|
+
store.on("write", write);
|
|
824
|
+
const prefix = store.path;
|
|
825
|
+
const tableOpts = getTableOpts(prefix[prefix.length - 1], rawOptions);
|
|
826
|
+
tableOpts.prefix = prefix;
|
|
795
827
|
const defaultDb = new Db(connection);
|
|
796
828
|
function read(query, options, next) {
|
|
797
829
|
const { pgClient } = options;
|
package/index.mjs
CHANGED
|
@@ -124,9 +124,7 @@ function construct(node, prop, op) {
|
|
|
124
124
|
return construct(val, prop, key);
|
|
125
125
|
}
|
|
126
126
|
if (prop) {
|
|
127
|
-
|
|
128
|
-
return construct(val, prop + key);
|
|
129
|
-
throw Error(`pgast.unexpected_prop: ${key}`);
|
|
127
|
+
return ["$sub", prop, construct({ [key]: val })];
|
|
130
128
|
}
|
|
131
129
|
return construct(val, key);
|
|
132
130
|
})
|
|
@@ -138,6 +136,8 @@ function simplify(node) {
|
|
|
138
136
|
node[1] = node[1].map((subnode) => simplify(subnode));
|
|
139
137
|
} else if (op === "$not") {
|
|
140
138
|
node[1] = simplify(node[1]);
|
|
139
|
+
} else if (op === "$sub") {
|
|
140
|
+
node[2] = simplify(node[2]);
|
|
141
141
|
}
|
|
142
142
|
if (op === "$and") {
|
|
143
143
|
if (!node[1].length)
|
|
@@ -383,36 +383,48 @@ function getBinarySql(lhs, type, op, value, textLhs) {
|
|
|
383
383
|
return sql`${lhs} ${sqlOp} ${cubeLiteralSql(value)}`;
|
|
384
384
|
return sql`${lhs} ${sqlOp} ${value}`;
|
|
385
385
|
}
|
|
386
|
-
function
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
386
|
+
function getNodeSql(ast, options) {
|
|
387
|
+
if (typeof ast === "boolean")
|
|
388
|
+
return ast;
|
|
389
|
+
const op = ast[0];
|
|
390
|
+
if (op === "$and" || op === "$or") {
|
|
391
|
+
return sql`(${join(
|
|
392
|
+
ast[1].map((node) => getNodeSql(node, options)),
|
|
393
|
+
`) ${opSql[op]} (`
|
|
394
|
+
)})`;
|
|
395
|
+
} else if (op === "$not") {
|
|
396
|
+
return sql`${opSql[op]} (${getNodeSql(ast[1], options)})`;
|
|
397
|
+
}
|
|
398
|
+
if (op === "$sub") {
|
|
399
|
+
const joinName = ast[1];
|
|
400
|
+
if (!options.joins[joinName])
|
|
401
|
+
throw Error(`pg.no_join ${joinName}`);
|
|
402
|
+
const { idCol, schema } = options;
|
|
403
|
+
const joinOptions = options.joins[joinName];
|
|
404
|
+
const { table: joinTable, refCol } = options.joins[joinName];
|
|
405
|
+
return sql`"${raw(idCol)}" IN (SELECT "${raw(refCol)}"::${raw(
|
|
406
|
+
schema.types[idCol]
|
|
407
|
+
)} FROM "${raw(joinTable)}" WHERE ${getNodeSql(ast[2], joinOptions)})`;
|
|
408
|
+
}
|
|
409
|
+
const [prefix, ...suffix] = ast[1].split(".");
|
|
410
|
+
const { types: types2 = {} } = options.schema;
|
|
411
|
+
if (!types2[prefix])
|
|
412
|
+
throw Error(`pg.no_column ${prefix}`);
|
|
413
|
+
if (types2[prefix] === "jsonb") {
|
|
414
|
+
const [lhs, textLhs] = suffix.length ? [
|
|
415
|
+
sql`"${raw(prefix)}" #> ${suffix}`,
|
|
416
|
+
sql`"${raw(prefix)}" #>> ${suffix}`
|
|
417
|
+
] : [sql`"${raw(prefix)}"`, sql`"${raw(prefix)}" #>> '{}'`];
|
|
418
|
+
return getBinarySql(lhs, "jsonb", op, ast[2], textLhs);
|
|
419
|
+
} else {
|
|
420
|
+
if (suffix.length)
|
|
421
|
+
throw Error(`pg.lookup_not_jsonb ${prefix}`);
|
|
422
|
+
return getBinarySql(sql`"${raw(prefix)}"`, types2[prefix], op, ast[2]);
|
|
414
423
|
}
|
|
415
|
-
|
|
424
|
+
}
|
|
425
|
+
function getSql(filter, options) {
|
|
426
|
+
const ast = getAst(filter);
|
|
427
|
+
return getNodeSql(ast, options);
|
|
416
428
|
}
|
|
417
429
|
const getIdMeta = ({ idCol, verDefault }) => sql`"${raw(idCol)}" AS "$key", ${raw(verDefault)} AS "$ver"`;
|
|
418
430
|
const getArgMeta = (key, { prefix, idCol, verDefault }) => sql`
|
|
@@ -656,10 +668,10 @@ class Db {
|
|
|
656
668
|
}
|
|
657
669
|
return res.rows[0];
|
|
658
670
|
}
|
|
659
|
-
async ensureSchema(tableOptions) {
|
|
671
|
+
async ensureSchema(tableOptions, typeOids) {
|
|
660
672
|
if (tableOptions.schema)
|
|
661
673
|
return;
|
|
662
|
-
const { table, verCol } = tableOptions;
|
|
674
|
+
const { table, verCol, joins } = tableOptions;
|
|
663
675
|
const tableSchema = (await this.query(sql`
|
|
664
676
|
SELECT table_schema
|
|
665
677
|
FROM information_schema.tables
|
|
@@ -674,7 +686,7 @@ class Db {
|
|
|
674
686
|
table_schema = ${tableSchema}`)).rows[0].column_types;
|
|
675
687
|
if (!types2)
|
|
676
688
|
throw Error(`pg.missing_table ${table}`);
|
|
677
|
-
|
|
689
|
+
typeOids = typeOids || (await this.query(sql`
|
|
678
690
|
SELECT jsonb_object_agg(typname, oid) AS type_oids
|
|
679
691
|
FROM pg_type
|
|
680
692
|
WHERE typname = 'cube'`)).rows[0].type_oids;
|
|
@@ -688,6 +700,11 @@ class Db {
|
|
|
688
700
|
if (!verDefault) {
|
|
689
701
|
throw Error(`pg.verCol_without_default ${verCol}`);
|
|
690
702
|
}
|
|
703
|
+
await Promise.all(
|
|
704
|
+
Object.values(joins).map(
|
|
705
|
+
(joinOptions) => this.ensureSchema(joinOptions, typeOids)
|
|
706
|
+
)
|
|
707
|
+
);
|
|
691
708
|
log("ensureSchema", types2);
|
|
692
709
|
tableOptions.schema = { types: types2, typeOids };
|
|
693
710
|
tableOptions.verDefault = verDefault;
|
|
@@ -778,18 +795,33 @@ class Db {
|
|
|
778
795
|
return result;
|
|
779
796
|
}
|
|
780
797
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
const tableOpts = {
|
|
786
|
-
prefix,
|
|
787
|
-
table: table || prefix[prefix.length - 1] || "default",
|
|
798
|
+
function getTableOpts(name, { table, idCol, verCol, joins, schema, verDefault } = {}, parentName = null) {
|
|
799
|
+
const tableName = table || name;
|
|
800
|
+
return {
|
|
801
|
+
table: table || name,
|
|
788
802
|
idCol: idCol || "id",
|
|
789
803
|
verCol: verCol || "updatedAt",
|
|
804
|
+
joins: Object.fromEntries(
|
|
805
|
+
Object.entries(joins || {}).map(
|
|
806
|
+
([joinName, { refCol = parentName, ...joinOptions }]) => [
|
|
807
|
+
joinName,
|
|
808
|
+
{
|
|
809
|
+
refCol,
|
|
810
|
+
...getTableOpts(joinName, joinOptions, tableName)
|
|
811
|
+
}
|
|
812
|
+
]
|
|
813
|
+
)
|
|
814
|
+
),
|
|
790
815
|
schema,
|
|
791
816
|
verDefault
|
|
792
817
|
};
|
|
818
|
+
}
|
|
819
|
+
const pg = ({ connection, ...rawOptions }) => (store) => {
|
|
820
|
+
store.on("read", read);
|
|
821
|
+
store.on("write", write);
|
|
822
|
+
const prefix = store.path;
|
|
823
|
+
const tableOpts = getTableOpts(prefix[prefix.length - 1], rawOptions);
|
|
824
|
+
tableOpts.prefix = prefix;
|
|
793
825
|
const defaultDb = new Db(connection);
|
|
794
826
|
function read(query, options, next) {
|
|
795
827
|
const { pgClient } = options;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@graffy/pg",
|
|
3
3
|
"description": "The standard Postgres module for Graffy. Each instance this module mounts a Postgres table as a Graffy subtree.",
|
|
4
4
|
"author": "aravind (https://github.com/aravindet)",
|
|
5
|
-
"version": "0.16.4
|
|
5
|
+
"version": "0.16.4",
|
|
6
6
|
"main": "./index.cjs",
|
|
7
7
|
"exports": {
|
|
8
8
|
"import": "./index.mjs",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"license": "Apache-2.0",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@graffy/common": "0.16.4
|
|
19
|
+
"@graffy/common": "0.16.4",
|
|
20
20
|
"debug": "^4.3.3"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
package/types/Db.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export default class Db {
|
|
|
4
4
|
query(sql: any, tableOptions: any): Promise<any>;
|
|
5
5
|
readSql(sql: any, tableOptions: any): Promise<any>;
|
|
6
6
|
writeSql(sql: any, tableOptions: any): Promise<any>;
|
|
7
|
-
ensureSchema(tableOptions: any): Promise<void>;
|
|
7
|
+
ensureSchema(tableOptions: any, typeOids: any): Promise<void>;
|
|
8
8
|
read(rootQuery: any, tableOptions: any): Promise<any>;
|
|
9
9
|
write(rootChange: any, tableOptions: any): Promise<any[]>;
|
|
10
10
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
export function pg({
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
export function pg({ connection, ...rawOptions }: Partial<TableOpts> & {
|
|
2
|
+
connection: any;
|
|
3
|
+
}): Function;
|
|
4
|
+
export type TableOpts = {
|
|
5
|
+
table: string;
|
|
6
|
+
idCol: string;
|
|
7
|
+
verCol: string;
|
|
8
|
+
joins: Record<string, Partial<TableOpts> & {
|
|
9
|
+
refCol: string;
|
|
10
|
+
}>;
|
|
6
11
|
schema?: any;
|
|
7
12
|
verDefault?: string;
|
|
8
|
-
}
|
|
13
|
+
};
|