graphile-bulk-mutations 0.2.0
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/LICENSE +23 -0
- package/README.md +197 -0
- package/augmentations.d.ts +102 -0
- package/augmentations.js +11 -0
- package/esm/augmentations.d.ts +102 -0
- package/esm/augmentations.js +9 -0
- package/esm/index.d.ts +30 -0
- package/esm/index.js +29 -0
- package/esm/plugins/BulkDeletePlugin.d.ts +13 -0
- package/esm/plugins/BulkDeletePlugin.js +181 -0
- package/esm/plugins/BulkInflectionPlugin.d.ts +9 -0
- package/esm/plugins/BulkInflectionPlugin.js +103 -0
- package/esm/plugins/BulkInsertPlugin.d.ts +21 -0
- package/esm/plugins/BulkInsertPlugin.js +214 -0
- package/esm/plugins/BulkRelationalPlugin.d.ts +31 -0
- package/esm/plugins/BulkRelationalPlugin.js +127 -0
- package/esm/plugins/BulkTypesPlugin.d.ts +18 -0
- package/esm/plugins/BulkTypesPlugin.js +391 -0
- package/esm/plugins/BulkUpdatePlugin.d.ts +13 -0
- package/esm/plugins/BulkUpdatePlugin.js +211 -0
- package/esm/plugins/BulkUpsertPlugin.d.ts +12 -0
- package/esm/plugins/BulkUpsertPlugin.js +165 -0
- package/esm/plugins/index.d.ts +7 -0
- package/esm/plugins/index.js +7 -0
- package/esm/preset.d.ts +35 -0
- package/esm/preset.js +74 -0
- package/esm/types.d.ts +40 -0
- package/esm/types.js +7 -0
- package/esm/utils/index.d.ts +4 -0
- package/esm/utils/index.js +2 -0
- package/esm/utils/relations.d.ts +42 -0
- package/esm/utils/relations.js +83 -0
- package/esm/utils/sql-builder.d.ts +56 -0
- package/esm/utils/sql-builder.js +121 -0
- package/index.d.ts +30 -0
- package/index.js +44 -0
- package/package.json +61 -0
- package/plugins/BulkDeletePlugin.d.ts +13 -0
- package/plugins/BulkDeletePlugin.js +184 -0
- package/plugins/BulkInflectionPlugin.d.ts +9 -0
- package/plugins/BulkInflectionPlugin.js +106 -0
- package/plugins/BulkInsertPlugin.d.ts +21 -0
- package/plugins/BulkInsertPlugin.js +217 -0
- package/plugins/BulkRelationalPlugin.d.ts +31 -0
- package/plugins/BulkRelationalPlugin.js +130 -0
- package/plugins/BulkTypesPlugin.d.ts +18 -0
- package/plugins/BulkTypesPlugin.js +394 -0
- package/plugins/BulkUpdatePlugin.d.ts +13 -0
- package/plugins/BulkUpdatePlugin.js +214 -0
- package/plugins/BulkUpsertPlugin.d.ts +12 -0
- package/plugins/BulkUpsertPlugin.js +168 -0
- package/plugins/index.d.ts +7 -0
- package/plugins/index.js +17 -0
- package/preset.d.ts +35 -0
- package/preset.js +77 -0
- package/types.d.ts +40 -0
- package/types.js +10 -0
- package/utils/index.d.ts +4 -0
- package/utils/index.js +9 -0
- package/utils/relations.d.ts +42 -0
- package/utils/relations.js +86 -0
- package/utils/sql-builder.d.ts +56 -0
- package/utils/sql-builder.js +126 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SQL builder utilities for bulk mutations.
|
|
4
|
+
*
|
|
5
|
+
* Provides parameterized SQL generation for VALUES clauses, ON CONFLICT
|
|
6
|
+
* clauses, and SET patches. Handles auto-batching when the parameter
|
|
7
|
+
* count would exceed PostgreSQL's 32K limit.
|
|
8
|
+
*
|
|
9
|
+
* IMPORTANT: All mutation queries use `RETURNING <pk_columns>` instead of
|
|
10
|
+
* `RETURNING *` to respect column-level SELECT grants. The caller is
|
|
11
|
+
* responsible for doing a follow-up SELECT using the returned PKs to
|
|
12
|
+
* resolve the full row through PostGraphile's grant-aware query pipeline.
|
|
13
|
+
* See: https://github.com/pyramation/graphile-column-privileges-mutations
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.buildBulkInsertSQL = buildBulkInsertSQL;
|
|
17
|
+
exports.buildBulkUpdateSQL = buildBulkUpdateSQL;
|
|
18
|
+
exports.buildBulkDeleteSQL = buildBulkDeleteSQL;
|
|
19
|
+
const types_1 = require("../types");
|
|
20
|
+
/**
|
|
21
|
+
* Builds parameterized INSERT ... VALUES SQL with optional ON CONFLICT clause.
|
|
22
|
+
* Auto-splits into multiple batches when parameter count exceeds PG_MAX_PARAMS.
|
|
23
|
+
*
|
|
24
|
+
* Uses `RETURNING <returningColumns>` (typically primary key columns) instead
|
|
25
|
+
* of `RETURNING *` to avoid permission errors with column-level SELECT grants.
|
|
26
|
+
*/
|
|
27
|
+
function buildBulkInsertSQL(tableName, columns, rows, returningColumns, onConflict) {
|
|
28
|
+
const colNames = columns.map((c) => `"${c.name}"`);
|
|
29
|
+
const colsPerRow = columns.length;
|
|
30
|
+
const maxRowsPerBatch = Math.floor(types_1.PG_MAX_PARAMS / colsPerRow);
|
|
31
|
+
const returningClause = returningColumns.length > 0
|
|
32
|
+
? returningColumns.map((c) => `"${c}"`).join(', ')
|
|
33
|
+
: '*';
|
|
34
|
+
const batches = [];
|
|
35
|
+
for (let offset = 0; offset < rows.length; offset += maxRowsPerBatch) {
|
|
36
|
+
const batchRows = rows.slice(offset, offset + maxRowsPerBatch);
|
|
37
|
+
const values = [];
|
|
38
|
+
const valuePlaceholders = [];
|
|
39
|
+
for (const row of batchRows) {
|
|
40
|
+
const placeholders = [];
|
|
41
|
+
for (const col of columns) {
|
|
42
|
+
const val = row[col.name];
|
|
43
|
+
if (val === undefined) {
|
|
44
|
+
placeholders.push('DEFAULT');
|
|
45
|
+
}
|
|
46
|
+
else if (val === null) {
|
|
47
|
+
placeholders.push('NULL');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
values.push(val);
|
|
51
|
+
placeholders.push(`$${values.length}::${col.sqlType}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
valuePlaceholders.push(`(${placeholders.join(', ')})`);
|
|
55
|
+
}
|
|
56
|
+
let text = `INSERT INTO ${tableName} (${colNames.join(', ')})\nVALUES ${valuePlaceholders.join(',\n ')}`;
|
|
57
|
+
if (onConflict) {
|
|
58
|
+
if (onConflict.conflictColumns && onConflict.conflictColumns.length > 0) {
|
|
59
|
+
const colList = onConflict.conflictColumns.map((c) => `"${c}"`).join(', ');
|
|
60
|
+
text += `\nON CONFLICT (${colList})`;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
text += '\nON CONFLICT';
|
|
64
|
+
}
|
|
65
|
+
if (onConflict.action === 'IGNORE') {
|
|
66
|
+
text += ' DO NOTHING';
|
|
67
|
+
}
|
|
68
|
+
else if (onConflict.action === 'UPDATE') {
|
|
69
|
+
const setCols = onConflict.updateColumns && onConflict.updateColumns.length > 0
|
|
70
|
+
? onConflict.updateColumns
|
|
71
|
+
: columns.map((c) => c.name);
|
|
72
|
+
const setClause = setCols
|
|
73
|
+
.map((c) => `"${c}" = EXCLUDED."${c}"`)
|
|
74
|
+
.join(', ');
|
|
75
|
+
text += ` DO UPDATE SET ${setClause}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
text += `\nRETURNING ${returningClause}`;
|
|
79
|
+
batches.push({ text, values });
|
|
80
|
+
}
|
|
81
|
+
return batches;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Builds a parameterized UPDATE ... SET ... WHERE SQL statement.
|
|
85
|
+
*
|
|
86
|
+
* Uses `RETURNING <returningColumns>` (typically primary key columns) instead
|
|
87
|
+
* of `RETURNING *` to avoid permission errors with column-level SELECT grants.
|
|
88
|
+
*/
|
|
89
|
+
function buildBulkUpdateSQL(tableName, patch, columns, returningColumns, whereClause, whereParams) {
|
|
90
|
+
const values = [];
|
|
91
|
+
const setClauses = [];
|
|
92
|
+
for (const [fieldName, value] of Object.entries(patch)) {
|
|
93
|
+
const col = columns.find((c) => c.name === fieldName);
|
|
94
|
+
if (!col)
|
|
95
|
+
continue;
|
|
96
|
+
if (value === undefined)
|
|
97
|
+
continue;
|
|
98
|
+
values.push(value);
|
|
99
|
+
setClauses.push(`"${col.name}" = $${values.length}::${col.sqlType}`);
|
|
100
|
+
}
|
|
101
|
+
if (setClauses.length === 0) {
|
|
102
|
+
throw new Error('No columns to update in patch');
|
|
103
|
+
}
|
|
104
|
+
// Renumber where params to follow set params
|
|
105
|
+
const offset = values.length;
|
|
106
|
+
const renumberedWhere = whereClause.replace(/\$(\d+)/g, (_, n) => `$${parseInt(n) + offset}`);
|
|
107
|
+
values.push(...whereParams);
|
|
108
|
+
const returningClause = returningColumns.length > 0
|
|
109
|
+
? returningColumns.map((c) => `"${c}"`).join(', ')
|
|
110
|
+
: '*';
|
|
111
|
+
const text = `UPDATE ${tableName}\nSET ${setClauses.join(', ')}\nWHERE ${renumberedWhere}\nRETURNING ${returningClause}`;
|
|
112
|
+
return { text, values };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Builds a parameterized DELETE ... WHERE SQL statement.
|
|
116
|
+
*
|
|
117
|
+
* Uses `RETURNING <returningColumns>` (typically primary key columns) instead
|
|
118
|
+
* of `RETURNING *` to avoid permission errors with column-level SELECT grants.
|
|
119
|
+
*/
|
|
120
|
+
function buildBulkDeleteSQL(tableName, returningColumns, whereClause, whereParams) {
|
|
121
|
+
const returningClause = returningColumns.length > 0
|
|
122
|
+
? returningColumns.map((c) => `"${c}"`).join(', ')
|
|
123
|
+
: '*';
|
|
124
|
+
const text = `DELETE FROM ${tableName}\nWHERE ${whereClause}\nRETURNING ${returningClause}`;
|
|
125
|
+
return { text, values: [...whereParams] };
|
|
126
|
+
}
|