csv-to-pg 3.3.4 → 3.4.1
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/esm/parse.js +75 -0
- package/esm/parser.js +5 -3
- package/esm/utils.js +12 -5
- package/package.json +2 -2
- package/parse.js +75 -0
- package/parser.d.ts +1 -0
- package/parser.js +5 -3
- package/utils.d.ts +4 -2
- package/utils.js +12 -5
package/esm/parse.js
CHANGED
|
@@ -29,6 +29,37 @@ const parseJson = (value) => {
|
|
|
29
29
|
return value;
|
|
30
30
|
return value ? JSON.stringify(value) : undefined;
|
|
31
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* Convert a PostgreSQL interval object to a PostgreSQL interval string.
|
|
34
|
+
* node-postgres returns intervals as objects like { hours: 12, minutes: 6, seconds: 41 }
|
|
35
|
+
*/
|
|
36
|
+
const formatInterval = (value) => {
|
|
37
|
+
if (typeof value === 'string') {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
if (value && typeof value === 'object') {
|
|
41
|
+
const interval = value;
|
|
42
|
+
const parts = [];
|
|
43
|
+
if (interval.years)
|
|
44
|
+
parts.push(`${interval.years} year${interval.years !== 1 ? 's' : ''}`);
|
|
45
|
+
if (interval.months)
|
|
46
|
+
parts.push(`${interval.months} mon${interval.months !== 1 ? 's' : ''}`);
|
|
47
|
+
if (interval.days)
|
|
48
|
+
parts.push(`${interval.days} day${interval.days !== 1 ? 's' : ''}`);
|
|
49
|
+
// Build time component
|
|
50
|
+
const hours = interval.hours || 0;
|
|
51
|
+
const minutes = interval.minutes || 0;
|
|
52
|
+
const seconds = interval.seconds || 0;
|
|
53
|
+
const milliseconds = interval.milliseconds || 0;
|
|
54
|
+
if (hours || minutes || seconds || milliseconds) {
|
|
55
|
+
const totalSeconds = seconds + milliseconds / 1000;
|
|
56
|
+
const timeStr = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${totalSeconds.toFixed(milliseconds ? 6 : 0).padStart(milliseconds ? 9 : 2, '0')}`;
|
|
57
|
+
parts.push(timeStr);
|
|
58
|
+
}
|
|
59
|
+
return parts.length > 0 ? parts.join(' ') : '00:00:00';
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
};
|
|
32
63
|
/**
|
|
33
64
|
* Escape a single array element for PostgreSQL array literal format.
|
|
34
65
|
* Handles: NULL, quotes, backslashes, commas, braces, and whitespace.
|
|
@@ -255,6 +286,50 @@ const getCoercionFunc = (type, from, opts, fieldName) => {
|
|
|
255
286
|
});
|
|
256
287
|
return wrapValue(val, opts);
|
|
257
288
|
};
|
|
289
|
+
case 'uuid[]':
|
|
290
|
+
return (record) => {
|
|
291
|
+
const rawValue = record[from[0]];
|
|
292
|
+
if (isNullToken(rawValue)) {
|
|
293
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, 'value is empty or null');
|
|
294
|
+
}
|
|
295
|
+
// Handle array values - validate each UUID
|
|
296
|
+
if (Array.isArray(rawValue)) {
|
|
297
|
+
if (rawValue.length === 0) {
|
|
298
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, 'array is empty');
|
|
299
|
+
}
|
|
300
|
+
const uuidRegex = /^([0-9a-fA-F]{8})-(([0-9a-fA-F]{4}-){3})([0-9a-fA-F]{12})$/i;
|
|
301
|
+
for (const item of rawValue) {
|
|
302
|
+
if (!uuidRegex.test(String(item))) {
|
|
303
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, `array contains invalid UUID: ${item}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const arrayLiteral = psqlArray(rawValue);
|
|
307
|
+
if (isEmpty(arrayLiteral)) {
|
|
308
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, 'failed to format array');
|
|
309
|
+
}
|
|
310
|
+
const val = nodes.aConst({
|
|
311
|
+
sval: ast.string({ sval: String(arrayLiteral) })
|
|
312
|
+
});
|
|
313
|
+
return wrapValue(val, opts);
|
|
314
|
+
}
|
|
315
|
+
// If not an array, treat as empty/null
|
|
316
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, 'value is not an array');
|
|
317
|
+
};
|
|
318
|
+
case 'interval':
|
|
319
|
+
return (record) => {
|
|
320
|
+
const rawValue = record[from[0]];
|
|
321
|
+
if (isNullToken(rawValue)) {
|
|
322
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, 'value is empty or null');
|
|
323
|
+
}
|
|
324
|
+
const value = formatInterval(rawValue);
|
|
325
|
+
if (isEmpty(value)) {
|
|
326
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, 'value is empty or invalid interval');
|
|
327
|
+
}
|
|
328
|
+
const val = nodes.aConst({
|
|
329
|
+
sval: ast.string({ sval: String(value) })
|
|
330
|
+
});
|
|
331
|
+
return wrapValue(val, opts);
|
|
332
|
+
};
|
|
258
333
|
case 'timestamp':
|
|
259
334
|
case 'timestamptz':
|
|
260
335
|
case 'date':
|
package/esm/parser.js
CHANGED
|
@@ -9,7 +9,7 @@ export class Parser {
|
|
|
9
9
|
}
|
|
10
10
|
async parse(data) {
|
|
11
11
|
const config = this.config;
|
|
12
|
-
const { schema, table, singleStmts, conflict, headers, delimeter } = config;
|
|
12
|
+
const { schema, table, singleStmts, conflict, conflictDoNothing, headers, delimeter } = config;
|
|
13
13
|
const opts = {};
|
|
14
14
|
if (headers)
|
|
15
15
|
opts.headers = headers;
|
|
@@ -44,7 +44,8 @@ export class Parser {
|
|
|
44
44
|
table,
|
|
45
45
|
types,
|
|
46
46
|
record,
|
|
47
|
-
conflict
|
|
47
|
+
conflict,
|
|
48
|
+
conflictDoNothing
|
|
48
49
|
}));
|
|
49
50
|
return deparse(stmts);
|
|
50
51
|
}
|
|
@@ -54,7 +55,8 @@ export class Parser {
|
|
|
54
55
|
table,
|
|
55
56
|
types,
|
|
56
57
|
records,
|
|
57
|
-
conflict
|
|
58
|
+
conflict,
|
|
59
|
+
conflictDoNothing
|
|
58
60
|
});
|
|
59
61
|
return deparse([stmt]);
|
|
60
62
|
}
|
package/esm/utils.js
CHANGED
|
@@ -106,7 +106,14 @@ const indexElem = (name) => ({
|
|
|
106
106
|
nulls_ordering: 'SORTBY_NULLS_DEFAULT'
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
|
-
const makeConflictClause = (conflictElems, fields) => {
|
|
109
|
+
const makeConflictClause = (conflictElems, fields, conflictDoNothing) => {
|
|
110
|
+
// If conflictDoNothing is true, generate ON CONFLICT DO NOTHING without specifying columns
|
|
111
|
+
// This catches any unique constraint violation
|
|
112
|
+
if (conflictDoNothing) {
|
|
113
|
+
return {
|
|
114
|
+
action: 'ONCONFLICT_NOTHING'
|
|
115
|
+
};
|
|
116
|
+
}
|
|
110
117
|
if (!conflictElems || !conflictElems.length)
|
|
111
118
|
return undefined;
|
|
112
119
|
const setElems = fields.filter((el) => !conflictElems.includes(el));
|
|
@@ -128,7 +135,7 @@ const makeConflictClause = (conflictElems, fields) => {
|
|
|
128
135
|
};
|
|
129
136
|
}
|
|
130
137
|
};
|
|
131
|
-
export const InsertOne = ({ schema = 'public', table, types, record, conflict }) => ({
|
|
138
|
+
export const InsertOne = ({ schema = 'public', table, types, record, conflict, conflictDoNothing }) => ({
|
|
132
139
|
RawStmt: {
|
|
133
140
|
stmt: {
|
|
134
141
|
InsertStmt: {
|
|
@@ -152,14 +159,14 @@ export const InsertOne = ({ schema = 'public', table, types, record, conflict })
|
|
|
152
159
|
limitOption: 'LIMIT_OPTION_DEFAULT'
|
|
153
160
|
}
|
|
154
161
|
},
|
|
155
|
-
onConflictClause: makeConflictClause(conflict, Object.keys(types)),
|
|
162
|
+
onConflictClause: makeConflictClause(conflict, Object.keys(types), conflictDoNothing),
|
|
156
163
|
override: 'OVERRIDING_NOT_SET'
|
|
157
164
|
}
|
|
158
165
|
},
|
|
159
166
|
stmt_len: 1
|
|
160
167
|
}
|
|
161
168
|
});
|
|
162
|
-
export const InsertMany = ({ schema = 'public', table, types, records, conflict }) => ({
|
|
169
|
+
export const InsertMany = ({ schema = 'public', table, types, records, conflict, conflictDoNothing }) => ({
|
|
163
170
|
RawStmt: {
|
|
164
171
|
stmt: {
|
|
165
172
|
InsertStmt: {
|
|
@@ -181,7 +188,7 @@ export const InsertMany = ({ schema = 'public', table, types, records, conflict
|
|
|
181
188
|
limitOption: 'LIMIT_OPTION_DEFAULT'
|
|
182
189
|
}
|
|
183
190
|
},
|
|
184
|
-
onConflictClause: makeConflictClause(conflict, Object.keys(types)),
|
|
191
|
+
onConflictClause: makeConflictClause(conflict, Object.keys(types), conflictDoNothing),
|
|
185
192
|
override: 'OVERRIDING_NOT_SET'
|
|
186
193
|
}
|
|
187
194
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "csv-to-pg",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
5
5
|
"description": "csv to pg statements",
|
|
6
6
|
"main": "index.js",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"js-yaml": "^3.14.0",
|
|
51
51
|
"pgsql-deparser": "^17.17.2"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "cf0d7de6f89b52e1e3dcd2cda068e7be27296615"
|
|
54
54
|
}
|
package/parse.js
CHANGED
|
@@ -35,6 +35,37 @@ const parseJson = (value) => {
|
|
|
35
35
|
return value;
|
|
36
36
|
return value ? JSON.stringify(value) : undefined;
|
|
37
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* Convert a PostgreSQL interval object to a PostgreSQL interval string.
|
|
40
|
+
* node-postgres returns intervals as objects like { hours: 12, minutes: 6, seconds: 41 }
|
|
41
|
+
*/
|
|
42
|
+
const formatInterval = (value) => {
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
if (value && typeof value === 'object') {
|
|
47
|
+
const interval = value;
|
|
48
|
+
const parts = [];
|
|
49
|
+
if (interval.years)
|
|
50
|
+
parts.push(`${interval.years} year${interval.years !== 1 ? 's' : ''}`);
|
|
51
|
+
if (interval.months)
|
|
52
|
+
parts.push(`${interval.months} mon${interval.months !== 1 ? 's' : ''}`);
|
|
53
|
+
if (interval.days)
|
|
54
|
+
parts.push(`${interval.days} day${interval.days !== 1 ? 's' : ''}`);
|
|
55
|
+
// Build time component
|
|
56
|
+
const hours = interval.hours || 0;
|
|
57
|
+
const minutes = interval.minutes || 0;
|
|
58
|
+
const seconds = interval.seconds || 0;
|
|
59
|
+
const milliseconds = interval.milliseconds || 0;
|
|
60
|
+
if (hours || minutes || seconds || milliseconds) {
|
|
61
|
+
const totalSeconds = seconds + milliseconds / 1000;
|
|
62
|
+
const timeStr = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${totalSeconds.toFixed(milliseconds ? 6 : 0).padStart(milliseconds ? 9 : 2, '0')}`;
|
|
63
|
+
parts.push(timeStr);
|
|
64
|
+
}
|
|
65
|
+
return parts.length > 0 ? parts.join(' ') : '00:00:00';
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
};
|
|
38
69
|
/**
|
|
39
70
|
* Escape a single array element for PostgreSQL array literal format.
|
|
40
71
|
* Handles: NULL, quotes, backslashes, commas, braces, and whitespace.
|
|
@@ -264,6 +295,50 @@ const getCoercionFunc = (type, from, opts, fieldName) => {
|
|
|
264
295
|
});
|
|
265
296
|
return (0, utils_2.wrapValue)(val, opts);
|
|
266
297
|
};
|
|
298
|
+
case 'uuid[]':
|
|
299
|
+
return (record) => {
|
|
300
|
+
const rawValue = record[from[0]];
|
|
301
|
+
if (isNullToken(rawValue)) {
|
|
302
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, 'value is empty or null');
|
|
303
|
+
}
|
|
304
|
+
// Handle array values - validate each UUID
|
|
305
|
+
if (Array.isArray(rawValue)) {
|
|
306
|
+
if (rawValue.length === 0) {
|
|
307
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, 'array is empty');
|
|
308
|
+
}
|
|
309
|
+
const uuidRegex = /^([0-9a-fA-F]{8})-(([0-9a-fA-F]{4}-){3})([0-9a-fA-F]{12})$/i;
|
|
310
|
+
for (const item of rawValue) {
|
|
311
|
+
if (!uuidRegex.test(String(item))) {
|
|
312
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, `array contains invalid UUID: ${item}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const arrayLiteral = psqlArray(rawValue);
|
|
316
|
+
if (isEmpty(arrayLiteral)) {
|
|
317
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, 'failed to format array');
|
|
318
|
+
}
|
|
319
|
+
const val = utils_1.nodes.aConst({
|
|
320
|
+
sval: utils_1.ast.string({ sval: String(arrayLiteral) })
|
|
321
|
+
});
|
|
322
|
+
return (0, utils_2.wrapValue)(val, opts);
|
|
323
|
+
}
|
|
324
|
+
// If not an array, treat as empty/null
|
|
325
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, 'value is not an array');
|
|
326
|
+
};
|
|
327
|
+
case 'interval':
|
|
328
|
+
return (record) => {
|
|
329
|
+
const rawValue = record[from[0]];
|
|
330
|
+
if (isNullToken(rawValue)) {
|
|
331
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, 'value is empty or null');
|
|
332
|
+
}
|
|
333
|
+
const value = formatInterval(rawValue);
|
|
334
|
+
if (isEmpty(value)) {
|
|
335
|
+
return makeNullOrThrow(fieldName, rawValue, type, required, 'value is empty or invalid interval');
|
|
336
|
+
}
|
|
337
|
+
const val = utils_1.nodes.aConst({
|
|
338
|
+
sval: utils_1.ast.string({ sval: String(value) })
|
|
339
|
+
});
|
|
340
|
+
return (0, utils_2.wrapValue)(val, opts);
|
|
341
|
+
};
|
|
267
342
|
case 'timestamp':
|
|
268
343
|
case 'timestamptz':
|
|
269
344
|
case 'date':
|
package/parser.d.ts
CHANGED
package/parser.js
CHANGED
|
@@ -12,7 +12,7 @@ class Parser {
|
|
|
12
12
|
}
|
|
13
13
|
async parse(data) {
|
|
14
14
|
const config = this.config;
|
|
15
|
-
const { schema, table, singleStmts, conflict, headers, delimeter } = config;
|
|
15
|
+
const { schema, table, singleStmts, conflict, conflictDoNothing, headers, delimeter } = config;
|
|
16
16
|
const opts = {};
|
|
17
17
|
if (headers)
|
|
18
18
|
opts.headers = headers;
|
|
@@ -47,7 +47,8 @@ class Parser {
|
|
|
47
47
|
table,
|
|
48
48
|
types,
|
|
49
49
|
record,
|
|
50
|
-
conflict
|
|
50
|
+
conflict,
|
|
51
|
+
conflictDoNothing
|
|
51
52
|
}));
|
|
52
53
|
return (0, pgsql_deparser_1.deparse)(stmts);
|
|
53
54
|
}
|
|
@@ -57,7 +58,8 @@ class Parser {
|
|
|
57
58
|
table,
|
|
58
59
|
types,
|
|
59
60
|
records,
|
|
60
|
-
conflict
|
|
61
|
+
conflict,
|
|
62
|
+
conflictDoNothing
|
|
61
63
|
});
|
|
62
64
|
return (0, pgsql_deparser_1.deparse)([stmt]);
|
|
63
65
|
}
|
package/utils.d.ts
CHANGED
|
@@ -11,16 +11,18 @@ interface InsertOneParams {
|
|
|
11
11
|
types: TypesMap;
|
|
12
12
|
record: Record<string, unknown>;
|
|
13
13
|
conflict?: string[];
|
|
14
|
+
conflictDoNothing?: boolean;
|
|
14
15
|
}
|
|
15
|
-
export declare const InsertOne: ({ schema, table, types, record, conflict }: InsertOneParams) => Node;
|
|
16
|
+
export declare const InsertOne: ({ schema, table, types, record, conflict, conflictDoNothing }: InsertOneParams) => Node;
|
|
16
17
|
interface InsertManyParams {
|
|
17
18
|
schema?: string;
|
|
18
19
|
table: string;
|
|
19
20
|
types: TypesMap;
|
|
20
21
|
records: Record<string, unknown>[];
|
|
21
22
|
conflict?: string[];
|
|
23
|
+
conflictDoNothing?: boolean;
|
|
22
24
|
}
|
|
23
|
-
export declare const InsertMany: ({ schema, table, types, records, conflict }: InsertManyParams) => Node;
|
|
25
|
+
export declare const InsertMany: ({ schema, table, types, records, conflict, conflictDoNothing }: InsertManyParams) => Node;
|
|
24
26
|
interface WrapOptions {
|
|
25
27
|
wrap?: string[];
|
|
26
28
|
wrapAst?: (val: Node) => Node;
|
package/utils.js
CHANGED
|
@@ -112,7 +112,14 @@ const indexElem = (name) => ({
|
|
|
112
112
|
nulls_ordering: 'SORTBY_NULLS_DEFAULT'
|
|
113
113
|
}
|
|
114
114
|
});
|
|
115
|
-
const makeConflictClause = (conflictElems, fields) => {
|
|
115
|
+
const makeConflictClause = (conflictElems, fields, conflictDoNothing) => {
|
|
116
|
+
// If conflictDoNothing is true, generate ON CONFLICT DO NOTHING without specifying columns
|
|
117
|
+
// This catches any unique constraint violation
|
|
118
|
+
if (conflictDoNothing) {
|
|
119
|
+
return {
|
|
120
|
+
action: 'ONCONFLICT_NOTHING'
|
|
121
|
+
};
|
|
122
|
+
}
|
|
116
123
|
if (!conflictElems || !conflictElems.length)
|
|
117
124
|
return undefined;
|
|
118
125
|
const setElems = fields.filter((el) => !conflictElems.includes(el));
|
|
@@ -134,7 +141,7 @@ const makeConflictClause = (conflictElems, fields) => {
|
|
|
134
141
|
};
|
|
135
142
|
}
|
|
136
143
|
};
|
|
137
|
-
const InsertOne = ({ schema = 'public', table, types, record, conflict }) => ({
|
|
144
|
+
const InsertOne = ({ schema = 'public', table, types, record, conflict, conflictDoNothing }) => ({
|
|
138
145
|
RawStmt: {
|
|
139
146
|
stmt: {
|
|
140
147
|
InsertStmt: {
|
|
@@ -158,7 +165,7 @@ const InsertOne = ({ schema = 'public', table, types, record, conflict }) => ({
|
|
|
158
165
|
limitOption: 'LIMIT_OPTION_DEFAULT'
|
|
159
166
|
}
|
|
160
167
|
},
|
|
161
|
-
onConflictClause: makeConflictClause(conflict, Object.keys(types)),
|
|
168
|
+
onConflictClause: makeConflictClause(conflict, Object.keys(types), conflictDoNothing),
|
|
162
169
|
override: 'OVERRIDING_NOT_SET'
|
|
163
170
|
}
|
|
164
171
|
},
|
|
@@ -166,7 +173,7 @@ const InsertOne = ({ schema = 'public', table, types, record, conflict }) => ({
|
|
|
166
173
|
}
|
|
167
174
|
});
|
|
168
175
|
exports.InsertOne = InsertOne;
|
|
169
|
-
const InsertMany = ({ schema = 'public', table, types, records, conflict }) => ({
|
|
176
|
+
const InsertMany = ({ schema = 'public', table, types, records, conflict, conflictDoNothing }) => ({
|
|
170
177
|
RawStmt: {
|
|
171
178
|
stmt: {
|
|
172
179
|
InsertStmt: {
|
|
@@ -188,7 +195,7 @@ const InsertMany = ({ schema = 'public', table, types, records, conflict }) => (
|
|
|
188
195
|
limitOption: 'LIMIT_OPTION_DEFAULT'
|
|
189
196
|
}
|
|
190
197
|
},
|
|
191
|
-
onConflictClause: makeConflictClause(conflict, Object.keys(types)),
|
|
198
|
+
onConflictClause: makeConflictClause(conflict, Object.keys(types), conflictDoNothing),
|
|
192
199
|
override: 'OVERRIDING_NOT_SET'
|
|
193
200
|
}
|
|
194
201
|
},
|