Skip to content

Commit 97035ad

Browse files
authored
Merge pull request #4 from lad-tech/copy
feat: copy method
2 parents fd2bd15 + 6bfb927 commit 97035ad

File tree

6 files changed

+161
-30
lines changed

6 files changed

+161
-30
lines changed

src/copy.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { adapter } from "./helpers/adapter";
2+
import { prepare } from "./helpers/prepare";
3+
import { Upsert2Params } from "./types";
4+
5+
export const copy = (
6+
data: Upsert2Params,
7+
config: { with: string }
8+
): { query: string; raw: string } => {
9+
const { table } = data;
10+
const { columns, stringValues } = prepare(adapter(data), ["copy"]);
11+
return {
12+
query: `COPY ${table.name} (${columns.join(
13+
", "
14+
)}) FROM STDIN with delimiter ',' ${config.with}`,
15+
raw: stringValues,
16+
};
17+
};

src/helpers/converter.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { TConfig as TConverterConfig } from '../types';
1+
import { TConfig as TConverterConfig } from "../types";
22

3-
const configs: TConverterConfig = {
3+
export const configs: TConverterConfig = {
44
number: {},
55
boolean: {},
66
raw: {},
77
null: {
88
process: (_) => null,
99
},
1010
array: {
11-
process: (value) => (value.length > 0 ? `{"${value.join(`", "`)}"}` : '{}'),
11+
process: (value) => (value.length > 0 ? `{"${value.join(`", "`)}"}` : "{}"),
1212
quote: true,
1313
},
1414
string: {
@@ -24,9 +24,39 @@ const configs: TConverterConfig = {
2424
},
2525
};
2626

27-
export const converter = (type: keyof TConverterConfig) => {
28-
const config = configs[type];
29-
if (!config) throw new Error(`Настройки для указанного типа '${type}' не найдены`);
27+
export const copyCfg: TConverterConfig = {
28+
number: {},
29+
boolean: {
30+
process: (value) => (value ? "t" : "f"),
31+
},
32+
raw: {},
33+
null: {
34+
process: (_) => null,
35+
},
36+
array: {
37+
process: (value) => (value.length > 0 ? `{"${value.join(`", "`)}"}` : "[]"),
38+
quote: true,
39+
},
40+
string: {
41+
quote: true,
42+
},
43+
json: {
44+
process: (value) => JSON.stringify(value),
45+
quote: true,
46+
},
47+
date: {
48+
process: (value) => value?.toISOString(),
49+
quote: true,
50+
},
51+
};
52+
53+
export const converter = (
54+
cfg: TConverterConfig,
55+
type: keyof TConverterConfig
56+
) => {
57+
const config = cfg[type];
58+
if (!config)
59+
throw new Error(`Настройки для указанного типа '${type}' не найдены`);
3060
const { process, ...rest } = config;
3161
return {
3262
process: (value: any): any => {

src/helpers/prepare.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,50 @@
1-
import { ToStorage } from '../types';
2-
import { ParamsEmptyCollector } from './collector';
3-
import { converter } from './converter';
1+
import { ToStorage } from "../types";
2+
import { ParamsEmptyCollector } from "./collector";
3+
import { configs, converter, copyCfg } from "./converter";
44

5-
export const prepare = ({ table, params, collector }: ToStorage, types: ('insert' | 'update')[] = ['insert', 'update']) => {
5+
export const prepare = (
6+
{ table, params, collector }: ToStorage,
7+
types: ("insert" | "update" | "copy")[] = ["insert", "update"]
8+
) => {
69
if (!(table.pk in params)) throw Error(`Required ${table.pk} in`);
710

811
const columns = [];
912
const values: any[] = [];
1013
const update: { key: string; value: any }[] = [];
1114
collector = collector ?? new ParamsEmptyCollector();
12-
1315
// eslint-disable-next-line prefer-const
1416
for (let [key, [value, type, updated]] of Object.entries(params)) {
15-
if (value === null) type = 'null';
17+
if (value === null) type = "null";
1618
let value_: any = value;
1719

18-
const willInsert = types.includes('insert');
19-
const willUpdate = updated && types.includes('update');
20+
const willInsert = types.includes("insert");
21+
const willCopy = types.includes("copy");
22+
const willUpdate = updated && types.includes("update");
2023

21-
const convert = converter(type);
24+
const cfg = willInsert || willUpdate ? configs : copyCfg;
25+
const convert = converter(cfg, type);
2226
value_ = convert.process(value_);
23-
if ((willUpdate || willInsert) && type !== 'raw') {
27+
if ((willUpdate || willInsert) && type !== "raw") {
2428
value_ = collector.param(value_, convert.quote, type);
2529
}
30+
2631
if (willInsert) {
2732
columns.push(`"${key}"`);
2833
values.push(value_);
2934
}
3035
if (willUpdate) {
3136
update.push({ key, value: value_ });
3237
}
38+
if (willCopy) {
39+
columns.push(`${key}`);
40+
values.push(value_ === null ? "null" : value_);
41+
}
3342
}
3443

35-
return { columns, values, update };
44+
return {
45+
columns,
46+
values,
47+
stringValues: values.join(",") + "\n",
48+
update,
49+
};
3650
};

src/index.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
export { insert } from './insert';
2-
export { update } from './update';
3-
export { upsert } from './upsert';
4-
export { upsert2 } from './upsert2';
5-
export { deleteSoft } from './deleteSoft';
6-
export { deleteHard } from './deleteHard';
7-
export { cte } from './helpers/cte';
8-
export { StorageType, ToStorage, Upsert2Params } from './types';
9-
export { Collector, ParamsCollector, ParamsEmptyCollector } from './helpers/collector';
1+
export { insert } from "./insert";
2+
export { copy } from "./copy";
3+
export { update } from "./update";
4+
export { upsert } from "./upsert";
5+
export { upsert2 } from "./upsert2";
6+
export { deleteSoft } from "./deleteSoft";
7+
export { deleteHard } from "./deleteHard";
8+
export { cte } from "./helpers/cte";
9+
export { StorageType, ToStorage, Upsert2Params } from "./types";
10+
export {
11+
Collector,
12+
ParamsCollector,
13+
ParamsEmptyCollector,
14+
} from "./helpers/collector";

src/upsert.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
import { prepare } from './helpers/prepare';
2-
import { ToStorage } from './types';
1+
import { prepare } from "./helpers/prepare";
2+
import { ToStorage } from "./types";
33

44
export const upsert = (data: ToStorage): string => {
55
const { table } = data;
66
const { columns, values, update } = prepare(data);
7-
let result = `INSERT INTO "${table.name}"\n` + `(${columns.join(', ')})\n` + `VALUES (${values.join(', ')})\n`;
7+
let result =
8+
`INSERT INTO "${table.name}"\n` +
9+
`(${columns.join(", ")})\n` +
10+
`VALUES (${values.join(", ")})\n`;
811

912
if (update.length > 0) {
10-
const updatePart = update.map(({key, value}) => `"${key}"=${value}`).join(',');
13+
const updatePart = update
14+
.map(({ key, value }) => `"${key}"=${value}`)
15+
.join(",");
1116
result += `ON CONFLICT ("${table.pk}") DO UPDATE SET ${updatePart}`;
1217
}
1318

tests/copy.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { copy } from "../src/copy";
2+
import { ParamsCollector } from "../src/helpers/collector";
3+
4+
describe("copy()", () => {
5+
it("JSONB", () => {
6+
const sql = copy(
7+
{
8+
table: { name: "table", pk: "table_id" },
9+
params: {
10+
table_id: "user_id",
11+
name: "User",
12+
list: [
13+
{
14+
id: "iRGMM496Pu4J_3PmHBIbw",
15+
common_use: true,
16+
parent_use: true,
17+
pointer_use: false,
18+
attribute_id: "dQTVm4zRFGdSVqLrra2bk",
19+
is_inherited: true,
20+
milestone_use: true,
21+
parent_change: false,
22+
common_require: false,
23+
parent_require: false,
24+
attribute_title: "Этаж",
25+
pointer_require: false,
26+
milestone_require: false,
27+
parent_inheritance: false,
28+
attribute_description: null,
29+
},
30+
],
31+
context: {
32+
code: "8",
33+
title: "Работа",
34+
sequence: 8,
35+
source_group: "Namespace",
36+
},
37+
bool: true,
38+
null: null,
39+
date: new Date(),
40+
},
41+
columns: {
42+
table_id: ["string", true],
43+
name: ["string", true],
44+
list: ["json", true],
45+
context: ["json", true],
46+
bool: ["boolean", true],
47+
null: ["string", true],
48+
date: ["date", true],
49+
},
50+
},
51+
{ with: `NULL AS 'null'` }
52+
);
53+
expect(sql.query).toContain(`COPY table (table_id, name, list, context`);
54+
expect(sql.query).toContain(`FROM STDIN with delimiter ',' NULL AS 'null'`);
55+
expect(sql.raw).toContain(`t,null,2024-10-01`);
56+
expect(sql.raw).toContain(
57+
`{"code":"8","title":"Работа","sequence":8,"source_group":"Namespace"}`
58+
);
59+
});
60+
});

0 commit comments

Comments
 (0)