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.
Files changed (63) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +197 -0
  3. package/augmentations.d.ts +102 -0
  4. package/augmentations.js +11 -0
  5. package/esm/augmentations.d.ts +102 -0
  6. package/esm/augmentations.js +9 -0
  7. package/esm/index.d.ts +30 -0
  8. package/esm/index.js +29 -0
  9. package/esm/plugins/BulkDeletePlugin.d.ts +13 -0
  10. package/esm/plugins/BulkDeletePlugin.js +181 -0
  11. package/esm/plugins/BulkInflectionPlugin.d.ts +9 -0
  12. package/esm/plugins/BulkInflectionPlugin.js +103 -0
  13. package/esm/plugins/BulkInsertPlugin.d.ts +21 -0
  14. package/esm/plugins/BulkInsertPlugin.js +214 -0
  15. package/esm/plugins/BulkRelationalPlugin.d.ts +31 -0
  16. package/esm/plugins/BulkRelationalPlugin.js +127 -0
  17. package/esm/plugins/BulkTypesPlugin.d.ts +18 -0
  18. package/esm/plugins/BulkTypesPlugin.js +391 -0
  19. package/esm/plugins/BulkUpdatePlugin.d.ts +13 -0
  20. package/esm/plugins/BulkUpdatePlugin.js +211 -0
  21. package/esm/plugins/BulkUpsertPlugin.d.ts +12 -0
  22. package/esm/plugins/BulkUpsertPlugin.js +165 -0
  23. package/esm/plugins/index.d.ts +7 -0
  24. package/esm/plugins/index.js +7 -0
  25. package/esm/preset.d.ts +35 -0
  26. package/esm/preset.js +74 -0
  27. package/esm/types.d.ts +40 -0
  28. package/esm/types.js +7 -0
  29. package/esm/utils/index.d.ts +4 -0
  30. package/esm/utils/index.js +2 -0
  31. package/esm/utils/relations.d.ts +42 -0
  32. package/esm/utils/relations.js +83 -0
  33. package/esm/utils/sql-builder.d.ts +56 -0
  34. package/esm/utils/sql-builder.js +121 -0
  35. package/index.d.ts +30 -0
  36. package/index.js +44 -0
  37. package/package.json +61 -0
  38. package/plugins/BulkDeletePlugin.d.ts +13 -0
  39. package/plugins/BulkDeletePlugin.js +184 -0
  40. package/plugins/BulkInflectionPlugin.d.ts +9 -0
  41. package/plugins/BulkInflectionPlugin.js +106 -0
  42. package/plugins/BulkInsertPlugin.d.ts +21 -0
  43. package/plugins/BulkInsertPlugin.js +217 -0
  44. package/plugins/BulkRelationalPlugin.d.ts +31 -0
  45. package/plugins/BulkRelationalPlugin.js +130 -0
  46. package/plugins/BulkTypesPlugin.d.ts +18 -0
  47. package/plugins/BulkTypesPlugin.js +394 -0
  48. package/plugins/BulkUpdatePlugin.d.ts +13 -0
  49. package/plugins/BulkUpdatePlugin.js +214 -0
  50. package/plugins/BulkUpsertPlugin.d.ts +12 -0
  51. package/plugins/BulkUpsertPlugin.js +168 -0
  52. package/plugins/index.d.ts +7 -0
  53. package/plugins/index.js +17 -0
  54. package/preset.d.ts +35 -0
  55. package/preset.js +77 -0
  56. package/types.d.ts +40 -0
  57. package/types.js +10 -0
  58. package/utils/index.d.ts +4 -0
  59. package/utils/index.js +9 -0
  60. package/utils/relations.d.ts +42 -0
  61. package/utils/relations.js +86 -0
  62. package/utils/sql-builder.d.ts +56 -0
  63. 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
+ }