csv-to-pg 0.6.2 → 2.0.2

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/module/parse.js DELETED
@@ -1,308 +0,0 @@
1
- import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
2
-
3
- function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
4
-
5
- function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
6
-
7
- import csv from 'csv-parser';
8
- import { createReadStream, readFileSync } from 'fs';
9
- import { safeLoad as parseYAML } from 'js-yaml';
10
- import * as ast from 'pg-ast';
11
- import { makeBoundingBox, makeLocation, getRelatedField, wrapValue } from './utils';
12
-
13
- function isNumeric(str) {
14
- if (typeof str != 'string') return false; // we only process strings!
15
-
16
- return !isNaN(str) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
17
- !isNaN(parseFloat(str)); // ...and ensure strings of whitespace fail
18
- }
19
-
20
- const parseJson = value => {
21
- if (typeof value === 'string') return value;
22
- return value && JSON.stringify(value);
23
- };
24
-
25
- const psqlArray = value => {
26
- if (value && value.length) {
27
- return `{${value.map(v => v)}}`;
28
- }
29
- };
30
-
31
- export const parse = (path, opts) => new Promise((resolve, reject) => {
32
- const results = [];
33
- createReadStream(path).pipe(csv(opts)) // TODO check if 'data' is guaranteed to have a full row,
34
- // if so, make a hook to use the stream properly
35
- .on('data', data => results.push(data)).on('error', er => {
36
- reject(er);
37
- }).on('end', () => {
38
- resolve(results);
39
- });
40
- });
41
- export const readConfig = config => {
42
- let configValue;
43
-
44
- if (config.endsWith('.js')) {
45
- configValue = require(config);
46
- } else if (config.endsWith('json')) {
47
- configValue = JSON.parse(readFileSync(config, 'utf-8'));
48
- } else if (config.endsWith('yaml') || config.endsWith('yml')) {
49
- configValue = parseYAML(readFileSync(config, 'utf-8'));
50
- } else {
51
- throw new Error('unsupported config!');
52
- }
53
-
54
- return configValue;
55
- };
56
-
57
- const getFromValue = from => {
58
- if (Array.isArray(from)) return from;
59
- return [from];
60
- }; // looks like the CSV library gives us empty strings?
61
-
62
-
63
- const cleanseEmptyStrings = str => {
64
- if (typeof str === 'string') {
65
- if (str.trim() === '') return null;
66
- return str;
67
- } else {
68
- return str;
69
- }
70
- };
71
-
72
- const parseBoolean = str => {
73
- if (typeof str === 'boolean') {
74
- return str;
75
- } else if (typeof str === 'string') {
76
- const s = str.toLowerCase();
77
-
78
- if (s === 'true') {
79
- return true;
80
- } else if (s === 't') {
81
- return true;
82
- } else if (s === 'f') {
83
- return false;
84
- } else if (s === 'false') {
85
- return false;
86
- }
87
-
88
- return null;
89
- } else {
90
- return null;
91
- }
92
- };
93
-
94
- const getValuesFromKeys = (object, keys) => keys.map(key => object[key]);
95
-
96
- const identity = a => a;
97
-
98
- const isEmpty = value => value === null || typeof value === 'undefined'; // type (int, text, etc)
99
- // from Array of keys that map to records found (e.g., ['lon', 'lat'])
100
-
101
-
102
- const getCoercionFunc = (type, from, opts) => {
103
- const parse = opts.parse = opts.parse || identity;
104
-
105
- switch (type) {
106
- case 'int':
107
- return record => {
108
- const value = parse(record[from[0]]);
109
-
110
- if (isEmpty(value)) {
111
- return ast.Null({});
112
- }
113
-
114
- if (!isNumeric(value)) {
115
- return ast.Null({});
116
- }
117
-
118
- const val = ast.A_Const({
119
- val: ast.Integer({
120
- ival: value
121
- })
122
- });
123
- return wrapValue(val, opts);
124
- };
125
-
126
- case 'float':
127
- return record => {
128
- const value = parse(record[from[0]]);
129
-
130
- if (isEmpty(value)) {
131
- return ast.Null({});
132
- }
133
-
134
- if (!isNumeric(value)) {
135
- return ast.Null({});
136
- }
137
-
138
- const val = ast.A_Const({
139
- val: ast.Float({
140
- str: value
141
- })
142
- });
143
- return wrapValue(val, opts);
144
- };
145
-
146
- case 'boolean':
147
- case 'bool':
148
- return record => {
149
- const value = parse(parseBoolean(record[from[0]]));
150
-
151
- if (isEmpty(value)) {
152
- return ast.Null({});
153
- }
154
-
155
- const val = ast.String({
156
- str: value ? 'TRUE' : 'FALSE'
157
- });
158
- return wrapValue(val, opts);
159
- };
160
-
161
- case 'bbox':
162
- // do bbox magic with args from the fields
163
- return record => {
164
- const val = makeBoundingBox(parse(record[from[0]]));
165
- return wrapValue(val, opts);
166
- };
167
-
168
- case 'location':
169
- return record => {
170
- const [lon, lat] = getValuesFromKeys(record, from);
171
-
172
- if (typeof lon === 'undefined') {
173
- return ast.Null({});
174
- }
175
-
176
- if (typeof lat === 'undefined') {
177
- return ast.Null({});
178
- }
179
-
180
- if (!isNumeric(lon) || !isNumeric(lat)) {
181
- return ast.Null({});
182
- } // NO parse here...
183
-
184
-
185
- const val = makeLocation(lon, lat);
186
- return wrapValue(val, opts);
187
- };
188
-
189
- case 'related':
190
- return record => {
191
- return getRelatedField(_objectSpread(_objectSpread({}, opts), {}, {
192
- record,
193
- from
194
- }));
195
- };
196
-
197
- case 'uuid':
198
- return record => {
199
- const value = parse(record[from[0]]);
200
-
201
- if (isEmpty(value) || !/^([0-9a-fA-F]{8})-(([0-9a-fA-F]{4}-){3})([0-9a-fA-F]{12})$/i.test(value)) {
202
- return ast.Null({});
203
- }
204
-
205
- const val = ast.A_Const({
206
- val: ast.String({
207
- str: value
208
- })
209
- });
210
- return wrapValue(val, opts);
211
- };
212
-
213
- case 'text':
214
- return record => {
215
- const value = parse(cleanseEmptyStrings(record[from[0]]));
216
-
217
- if (isEmpty(value)) {
218
- return ast.Null({});
219
- }
220
-
221
- const val = ast.A_Const({
222
- val: ast.String({
223
- str: value
224
- })
225
- });
226
- return wrapValue(val, opts);
227
- };
228
-
229
- case 'text[]':
230
- return record => {
231
- const value = parse(psqlArray(cleanseEmptyStrings(record[from[0]])));
232
-
233
- if (isEmpty(value)) {
234
- return ast.Null({});
235
- }
236
-
237
- const val = ast.A_Const({
238
- val: ast.String({
239
- str: value
240
- })
241
- });
242
- return wrapValue(val, opts);
243
- };
244
-
245
- case 'image':
246
- case 'attachment':
247
- case 'json':
248
- case 'jsonb':
249
- return record => {
250
- const value = parse(parseJson(cleanseEmptyStrings(record[from[0]])));
251
-
252
- if (isEmpty(value)) {
253
- return ast.Null({});
254
- }
255
-
256
- const val = ast.A_Const({
257
- val: ast.String({
258
- str: value
259
- })
260
- });
261
- return wrapValue(val, opts);
262
- };
263
-
264
- default:
265
- return record => {
266
- const value = parse(cleanseEmptyStrings(record[from[0]]));
267
-
268
- if (isEmpty(value)) {
269
- return ast.Null({});
270
- }
271
-
272
- const val = ast.A_Const({
273
- val: ast.String({
274
- str: value
275
- })
276
- });
277
- return wrapValue(val, opts);
278
- };
279
- }
280
- };
281
-
282
- export const parseTypes = config => {
283
- return Object.entries(config.fields).reduce((m, v) => {
284
- let [key, value] = v;
285
- let type;
286
- let from;
287
-
288
- if (typeof value === 'string') {
289
- type = value;
290
- from = [key];
291
-
292
- if (['related', 'location'].includes(type)) {
293
- throw new Error('must use object for ' + type + ' type');
294
- }
295
-
296
- value = {
297
- type,
298
- from
299
- };
300
- } else {
301
- type = value.type;
302
- from = getFromValue(value.from || key);
303
- }
304
-
305
- m[key] = getCoercionFunc(type, from, value);
306
- return m;
307
- }, {});
308
- };
package/module/parser.js DELETED
@@ -1,67 +0,0 @@
1
- import { readFileSync, writeFileSync } from 'fs';
2
- import { parse, parseTypes } from './index';
3
- import { deparse } from 'pgsql-deparser';
4
- import { InsertOne, InsertMany } from './utils';
5
- export class Parser {
6
- constructor(config) {
7
- this.config = config;
8
- }
9
-
10
- async parse(data) {
11
- const config = this.config;
12
- const {
13
- schema,
14
- table,
15
- singleStmts,
16
- conflict,
17
- headers,
18
- delimeter
19
- } = config;
20
- const opts = {};
21
- if (headers) opts.headers = headers;
22
- if (delimeter) opts.separator = delimeter;
23
- let records;
24
-
25
- if (typeof data === 'undefined') {
26
- if (config.json || config.input.endsWith('.json')) {
27
- records = JSON.parse(readFileSync(config.input, 'utf-8'));
28
- } else {
29
- records = await parse(config.input, opts);
30
- }
31
- } else {
32
- if (!Array.isArray(data)) {
33
- throw new Error('data is not an array');
34
- }
35
-
36
- records = data;
37
- }
38
-
39
- if (config.debug) {
40
- console.log(records);
41
- return;
42
- }
43
-
44
- const types = parseTypes(config);
45
-
46
- if (singleStmts) {
47
- const stmts = records.map(record => InsertOne({
48
- schema,
49
- table,
50
- types,
51
- record,
52
- conflict
53
- }));
54
- return deparse(stmts);
55
- } else {
56
- const stmt = InsertMany({
57
- schema,
58
- table,
59
- types,
60
- records,
61
- conflict
62
- });
63
- return deparse([stmt]);
64
- }
65
- }
66
-
67
- }
package/module/utils.js DELETED
@@ -1,334 +0,0 @@
1
- import * as ast from 'pg-ast';
2
- import { join, resolve } from 'path';
3
- export const normalizePath = (path, cwd) => path.startsWith('/') ? path : resolve(join(cwd ? cwd : process.cwd(), path));
4
-
5
- const lstr = bbox => {
6
- const [lng1, lat1, lng2, lat2] = bbox.split(',').map(a => a.trim());
7
- return `LINESTRING(${lng1} ${lat1}, ${lng1} ${lat2}, ${lng2} ${lat2}, ${lng2} ${lat1}, ${lng1} ${lat1})`;
8
- };
9
-
10
- const funcCall = (name, args) => {
11
- return ast.FuncCall({
12
- funcname: [ast.String({
13
- str: name
14
- })],
15
- args
16
- });
17
- };
18
-
19
- const aflt = num => ast.A_Const({
20
- val: ast.Float({
21
- str: num
22
- })
23
- });
24
-
25
- const aint = num => ast.A_Const({
26
- val: ast.Integer({
27
- ival: num
28
- })
29
- });
30
-
31
- export const makeLocation = (longitude, latitude) => {
32
- if (!longitude || !latitude) {
33
- return ast.Null();
34
- }
35
-
36
- return funcCall('st_setsrid', [funcCall('st_makepoint', [aflt(longitude), aflt(latitude)]), aint(4326)]);
37
- }; // a string in the form of lon,lat,lon,lat
38
- // -118.587533,34.024999,-118.495177,34.13165
39
-
40
- export const makeBoundingBox = bbox => {
41
- return funcCall('st_setsrid', [funcCall('st_makepolygon', [funcCall('st_geomfromtext', [ast.A_Const({
42
- val: ast.String({
43
- str: lstr(bbox)
44
- })
45
- })])]), aint(4326)]);
46
- };
47
-
48
- const ValuesLists = ({
49
- types,
50
- record
51
- }) => Object.entries(types).map(([field, type]) => {
52
- if (typeof type === 'function') {
53
- return type(record);
54
- }
55
-
56
- throw new Error('coercion function missing');
57
- });
58
-
59
- const makeCast = (arg, type) => ({
60
- TypeCast: {
61
- arg,
62
- typeName: {
63
- TypeName: {
64
- names: [{
65
- String: {
66
- str: type
67
- }
68
- }],
69
- typemod: -1
70
- }
71
- }
72
- }
73
- });
74
-
75
- const ref = name => ({
76
- ResTarget: {
77
- name,
78
- val: {
79
- ColumnRef: {
80
- fields: [{
81
- String: {
82
- str: 'excluded'
83
- }
84
- }, {
85
- String: {
86
- str: name
87
- }
88
- }]
89
- }
90
- }
91
- }
92
- });
93
-
94
- const indexElem = name => ({
95
- IndexElem: {
96
- name,
97
- ordering: 0,
98
- nulls_ordering: 0
99
- }
100
- });
101
-
102
- const makeConflictClause = (conflictElems, fields) => {
103
- if (!conflictElems || !conflictElems.length) return undefined;
104
- const setElems = fields.filter(el => !conflictElems.includes(el));
105
-
106
- if (setElems.length) {
107
- return {
108
- OnConflictClause: {
109
- action: 2,
110
- infer: {
111
- InferClause: {
112
- indexElems: conflictElems.map(a => indexElem(a))
113
- }
114
- },
115
- targetList: setElems.map(a => ref(a))
116
- }
117
- };
118
- } else {
119
- return {
120
- OnConflictClause: {
121
- action: 1,
122
- infer: {
123
- InferClause: {
124
- indexElems: conflictElems.map(a => indexElem(a))
125
- }
126
- }
127
- }
128
- };
129
- }
130
- };
131
-
132
- export const InsertOne = ({
133
- schema = 'public',
134
- table,
135
- types,
136
- record,
137
- conflict
138
- }) => ({
139
- RawStmt: {
140
- stmt: {
141
- InsertStmt: {
142
- relation: {
143
- RangeVar: {
144
- schemaname: schema,
145
- relname: table,
146
- inh: true,
147
- relpersistence: 'p'
148
- }
149
- },
150
- cols: Object.keys(types).map(field => ast.ResTarget({
151
- name: field
152
- })),
153
- selectStmt: {
154
- SelectStmt: {
155
- valuesLists: [ValuesLists({
156
- types,
157
- record
158
- })],
159
- op: 0
160
- }
161
- },
162
- onConflictClause: makeConflictClause(conflict, Object.keys(types)),
163
- override: 0
164
- }
165
- },
166
- stmt_len: 1
167
- }
168
- });
169
- export const InsertMany = ({
170
- schema = 'public',
171
- table,
172
- types,
173
- records,
174
- conflict
175
- }) => ({
176
- RawStmt: {
177
- stmt: {
178
- InsertStmt: {
179
- relation: {
180
- RangeVar: {
181
- schemaname: schema,
182
- relname: table,
183
- inh: true,
184
- relpersistence: 'p'
185
- }
186
- },
187
- cols: Object.keys(types).map(field => ast.ResTarget({
188
- name: field
189
- })),
190
- selectStmt: {
191
- SelectStmt: {
192
- valuesLists: records.map(record => ValuesLists({
193
- types,
194
- record
195
- })),
196
- op: 0
197
- }
198
- },
199
- onConflictClause: makeConflictClause(conflict, Object.keys(types)),
200
- override: 0
201
- }
202
- },
203
- stmt_len: 1
204
- }
205
- });
206
- export const wrapValue = (val, {
207
- wrap,
208
- wrapAst,
209
- cast
210
- } = {}) => {
211
- if (Array.isArray(wrap)) {
212
- val = ast.FuncCall({
213
- funcname: wrap.map(n => ast.String({
214
- str: n
215
- })),
216
- args: [val]
217
- });
218
- }
219
-
220
- if (wrapAst) return wrapAst(val);
221
- if (cast) return makeCast(val, cast);
222
- return val;
223
- };
224
- export const getRelatedField = ({
225
- schema = 'public',
226
- table,
227
- refType,
228
- refKey,
229
- refField,
230
- wrap,
231
- wrapAst,
232
- cast,
233
- record,
234
- parse,
235
- from
236
- }) => {
237
- let val;
238
- const value = parse(record[from[0]]);
239
-
240
- if (typeof value === 'undefined') {
241
- return ast.Null({});
242
- }
243
-
244
- switch (refType) {
245
- case 'int':
246
- val = ast.A_Const({
247
- val: ast.Integer({
248
- ival: value
249
- })
250
- });
251
- break;
252
-
253
- case 'float':
254
- val = ast.A_Const({
255
- val: ast.Float({
256
- str: value
257
- })
258
- });
259
- break;
260
-
261
- case 'boolean':
262
- case 'bool':
263
- val = ast.String({
264
- str: value ? 'TRUE' : 'FALSE'
265
- });
266
- break;
267
-
268
- case 'text':
269
- default:
270
- val = ast.A_Const({
271
- val: ast.String({
272
- str: value
273
- })
274
- });
275
- }
276
-
277
- val = wrapValue(val, {
278
- wrap,
279
- wrapAst
280
- });
281
- return wrapValue({
282
- SubLink: {
283
- subLinkType: 4,
284
- subselect: {
285
- SelectStmt: {
286
- targetList: [{
287
- ResTarget: {
288
- val: {
289
- ColumnRef: {
290
- fields: [{
291
- String: {
292
- str: refKey
293
- }
294
- }]
295
- }
296
- }
297
- }
298
- }],
299
- fromClause: [{
300
- RangeVar: {
301
- schemaname: schema,
302
- relname: table,
303
- inh: true,
304
- relpersistence: 'p'
305
- }
306
- }],
307
- whereClause: {
308
- A_Expr: {
309
- kind: 0,
310
- name: [{
311
- String: {
312
- str: '='
313
- }
314
- }],
315
- lexpr: {
316
- ColumnRef: {
317
- fields: [{
318
- String: {
319
- str: refField
320
- }
321
- }]
322
- }
323
- },
324
- rexpr: val
325
- }
326
- },
327
- op: 0
328
- }
329
- }
330
- }
331
- }, {
332
- cast
333
- });
334
- };