chore: working update for relationships, locales, base row
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Create } from 'payload/dist/database/types';
|
||||
import toSnakeCase from 'to-snake-case';
|
||||
import { upsertRow } from '../upsertRow';
|
||||
import { insertRow } from '../insertRow';
|
||||
|
||||
export const create: Create = async function create({
|
||||
collection: collectionSlug,
|
||||
@@ -9,13 +9,12 @@ export const create: Create = async function create({
|
||||
}) {
|
||||
const collection = this.payload.collections[collectionSlug].config;
|
||||
|
||||
const result = await upsertRow({
|
||||
const result = await insertRow({
|
||||
adapter: this,
|
||||
data,
|
||||
fallbackLocale: req.fallbackLocale,
|
||||
fields: collection.fields,
|
||||
locale: req.locale,
|
||||
operation: 'create',
|
||||
tableName: toSnakeCase(collectionSlug),
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { PayloadRequest } from 'payload/dist/express/types';
|
||||
import type { SanitizedCollectionConfig } from 'payload/dist/collections/config/types';
|
||||
import buildQuery from './queries/buildQuery';
|
||||
import { buildFindManyArgs } from './find/buildFindManyArgs';
|
||||
import { transform } from './transform';
|
||||
import { transform } from './transform/read';
|
||||
|
||||
export const findOne: FindOne = async function findOne({
|
||||
collection,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { PostgresAdapter } from '../types';
|
||||
import { ArrayRowToInsert } from './types';
|
||||
import { PostgresAdapter } from './types';
|
||||
import { ArrayRowToInsert } from './transform/write/types';
|
||||
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
185
packages/db-postgres/src/insertRow/index.ts
Normal file
185
packages/db-postgres/src/insertRow/index.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { Field } from 'payload/types';
|
||||
import { PostgresAdapter } from '../types';
|
||||
import { transform } from '../transform/read';
|
||||
import { BlockRowToInsert } from '../transform/write/types';
|
||||
import { insertArrays } from '../insertArrays';
|
||||
import { transformForWrite } from '../transform/write';
|
||||
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
data: Record<string, unknown>
|
||||
fallbackLocale?: string | false
|
||||
fields: Field[]
|
||||
locale: string
|
||||
path?: string
|
||||
tableName: string
|
||||
}
|
||||
|
||||
export const insertRow = async ({
|
||||
adapter,
|
||||
data,
|
||||
fallbackLocale,
|
||||
fields,
|
||||
locale,
|
||||
path = '',
|
||||
tableName,
|
||||
}: Args): Promise<Record<string, unknown>> => {
|
||||
// Split out the incoming data into the corresponding:
|
||||
// base row, locales, relationships, blocks, and arrays
|
||||
const rowToInsert = transformForWrite({
|
||||
data,
|
||||
fields,
|
||||
locale,
|
||||
path,
|
||||
tableName,
|
||||
});
|
||||
|
||||
// First, we insert the main row
|
||||
const [insertedRow] = await adapter.db.insert(adapter.tables[tableName])
|
||||
.values(rowToInsert.row).returning();
|
||||
|
||||
|
||||
let localeToInsert: Record<string, unknown>;
|
||||
const relationsToInsert: Record<string, unknown>[] = [];
|
||||
const blocksToInsert: { [blockType: string]: BlockRowToInsert[] } = {};
|
||||
|
||||
// Maintain a list of promises to run locale, blocks, and relationships
|
||||
// all in parallel
|
||||
const promises = [];
|
||||
|
||||
// If there is a locale row with data, add the parent and locale
|
||||
if (Object.keys(rowToInsert.locale).length > 0) {
|
||||
rowToInsert.locale._parentID = insertedRow.id;
|
||||
rowToInsert.locale._locale = locale;
|
||||
localeToInsert = rowToInsert.locale;
|
||||
}
|
||||
|
||||
// If there are relationships, add parent to each
|
||||
if (rowToInsert.relationships.length > 0) {
|
||||
rowToInsert.relationships.forEach((relation) => {
|
||||
relation.parent = insertedRow.id;
|
||||
relationsToInsert.push(relation);
|
||||
});
|
||||
}
|
||||
|
||||
// If there are blocks, add parent to each, and then
|
||||
// store by table name and rows
|
||||
Object.keys(rowToInsert.blocks).forEach((blockName) => {
|
||||
rowToInsert.blocks[blockName].forEach((blockRow) => {
|
||||
blockRow.row._parentID = insertedRow.id;
|
||||
if (!blocksToInsert[blockName]) blocksToInsert[blockName] = [];
|
||||
blocksToInsert[blockName].push(blockRow);
|
||||
});
|
||||
});
|
||||
|
||||
// //////////////////////////////////
|
||||
// INSERT LOCALES
|
||||
// //////////////////////////////////
|
||||
|
||||
let insertedLocaleRow;
|
||||
|
||||
if (localeToInsert) {
|
||||
promises.push(async () => {
|
||||
[insertedLocaleRow] = await adapter.db.insert(adapter.tables[`${tableName}_locales`])
|
||||
.values(localeToInsert).returning();
|
||||
});
|
||||
}
|
||||
|
||||
// //////////////////////////////////
|
||||
// INSERT RELATIONSHIPS
|
||||
// //////////////////////////////////
|
||||
|
||||
let insertedRelationshipRows: Record<string, unknown>[];
|
||||
|
||||
if (relationsToInsert.length > 0) {
|
||||
promises.push(async () => {
|
||||
insertedRelationshipRows = await adapter.db.insert(adapter.tables[`${tableName}_relationships`])
|
||||
.values(relationsToInsert).returning();
|
||||
});
|
||||
}
|
||||
|
||||
// //////////////////////////////////
|
||||
// INSERT BLOCKS
|
||||
// //////////////////////////////////
|
||||
|
||||
const insertedBlockRows: Record<string, Record<string, unknown>[]> = {};
|
||||
|
||||
Object.entries(blocksToInsert).forEach(([blockName, blockRows]) => {
|
||||
// For each block, push insert into promises to run parallel
|
||||
promises.push(async () => {
|
||||
insertedBlockRows[blockName] = await adapter.db.insert(adapter.tables[`${tableName}_${blockName}`])
|
||||
.values(blockRows.map(({ row }) => row)).returning();
|
||||
|
||||
insertedBlockRows[blockName].forEach((row, i) => {
|
||||
delete row._parentID;
|
||||
blockRows[i].row = row;
|
||||
});
|
||||
|
||||
const blockLocaleIndexMap: number[] = [];
|
||||
|
||||
const blockLocaleRowsToInsert = blockRows.reduce((acc, blockRow, i) => {
|
||||
if (Object.keys(blockRow.locale).length > 0) {
|
||||
blockRow.locale._parentID = blockRow.row.id;
|
||||
blockRow.locale._locale = locale;
|
||||
acc.push(blockRow.locale);
|
||||
blockLocaleIndexMap.push(i);
|
||||
return acc;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (blockLocaleRowsToInsert.length > 0) {
|
||||
const insertedBlockLocaleRows = await adapter.db.insert(adapter.tables[`${tableName}_${blockName}_locales`])
|
||||
.values(blockLocaleRowsToInsert).returning();
|
||||
|
||||
insertedBlockLocaleRows.forEach((blockLocaleRow, i) => {
|
||||
delete blockLocaleRow._parentID;
|
||||
insertedBlockRows[blockName][blockLocaleIndexMap[i]]._locales = [blockLocaleRow];
|
||||
});
|
||||
}
|
||||
|
||||
await insertArrays({
|
||||
adapter,
|
||||
arrays: blockRows.map(({ arrays }) => arrays),
|
||||
parentRows: insertedBlockRows[blockName],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// //////////////////////////////////
|
||||
// INSERT ARRAYS RECURSIVELY
|
||||
// //////////////////////////////////
|
||||
|
||||
promises.push(async () => {
|
||||
await insertArrays({
|
||||
adapter,
|
||||
arrays: [rowToInsert.arrays],
|
||||
parentRows: [insertedRow],
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(promises.map((promise) => promise()));
|
||||
|
||||
// //////////////////////////////////
|
||||
// TRANSFORM DATA
|
||||
// //////////////////////////////////
|
||||
|
||||
if (insertedLocaleRow) insertedRow._locales = [insertedLocaleRow];
|
||||
if (insertedRelationshipRows?.length > 0) insertedRow._relationships = insertedRelationshipRows;
|
||||
|
||||
Object.entries(insertedBlockRows).forEach(([blockName, blocks]) => {
|
||||
if (blocks.length > 0) insertedRow[`_blocks_${blockName}`] = blocks;
|
||||
});
|
||||
|
||||
const result = transform({
|
||||
config: adapter.payload.config,
|
||||
data: insertedRow,
|
||||
fallbackLocale,
|
||||
fields,
|
||||
locale,
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
numeric,
|
||||
timestamp,
|
||||
IndexBuilder,
|
||||
unique,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { Field } from 'payload/types';
|
||||
import toSnakeCase from 'to-snake-case';
|
||||
@@ -109,7 +110,9 @@ export const buildTable = ({
|
||||
return Object.entries(localesIndexes).reduce((acc, [colName, func]) => {
|
||||
acc[colName] = func(cols);
|
||||
return acc;
|
||||
}, {});
|
||||
}, {
|
||||
_localeParent: unique().on(cols._locale, cols._parentID),
|
||||
});
|
||||
});
|
||||
|
||||
adapter.tables[localeTableName] = localesTable;
|
||||
|
||||
@@ -3,9 +3,9 @@ import { Field } from 'payload/types';
|
||||
import { TypeWithID } from 'payload/dist/collections/config/types';
|
||||
import { SanitizedConfig } from 'payload/config';
|
||||
import { traverseFields } from './traverseFields';
|
||||
import { createRelationshipMap } from '../utilities/createRelationshipMap';
|
||||
import { createRelationshipMap } from '../../utilities/createRelationshipMap';
|
||||
import { mergeLocales } from './mergeLocales';
|
||||
import { createBlocksMap } from '../utilities/createBlocksMap';
|
||||
import { createBlocksMap } from '../../utilities/createBlocksMap';
|
||||
|
||||
type TransformArgs = {
|
||||
config: SanitizedConfig
|
||||
49
packages/db-postgres/src/transform/write/index.ts
Normal file
49
packages/db-postgres/src/transform/write/index.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { Field } from 'payload/types';
|
||||
import { traverseFields } from './traverseFields';
|
||||
import { RowToInsert } from './types';
|
||||
|
||||
type Args = {
|
||||
data: Record<string, unknown>
|
||||
fields: Field[]
|
||||
locale: string
|
||||
path?: string
|
||||
tableName: string
|
||||
}
|
||||
|
||||
export const transformForWrite = ({
|
||||
data,
|
||||
fields,
|
||||
locale,
|
||||
path = '',
|
||||
tableName,
|
||||
}: Args): RowToInsert => {
|
||||
// Split out the incoming data into the corresponding:
|
||||
// base row, locales, relationships, blocks, and arrays
|
||||
const rowToInsert: RowToInsert = {
|
||||
row: {},
|
||||
locale: {},
|
||||
relationships: [],
|
||||
blocks: {},
|
||||
arrays: {},
|
||||
};
|
||||
|
||||
// This function is responsible for building up the
|
||||
// above rowToInsert
|
||||
traverseFields({
|
||||
arrays: rowToInsert.arrays,
|
||||
blocks: rowToInsert.blocks,
|
||||
columnPrefix: '',
|
||||
data,
|
||||
fields,
|
||||
locale,
|
||||
localeRow: rowToInsert.locale,
|
||||
newTableName: tableName,
|
||||
parentTableName: tableName,
|
||||
path,
|
||||
relationships: rowToInsert.relationships,
|
||||
row: rowToInsert.row,
|
||||
});
|
||||
|
||||
return rowToInsert;
|
||||
};
|
||||
@@ -2,12 +2,10 @@
|
||||
import { Field } from 'payload/types';
|
||||
import toSnakeCase from 'to-snake-case';
|
||||
import { fieldAffectsData, valueIsValueWithRelation } from 'payload/dist/fields/config/types';
|
||||
import { PostgresAdapter } from '../types';
|
||||
import { ArrayRowToInsert, BlockRowToInsert } from './types';
|
||||
import { isArrayOfRows } from '../utilities/isArrayOfRows';
|
||||
import { isArrayOfRows } from '../../utilities/isArrayOfRows';
|
||||
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
arrays: {
|
||||
[tableName: string]: ArrayRowToInsert[]
|
||||
}
|
||||
@@ -27,7 +25,6 @@ type Args = {
|
||||
}
|
||||
|
||||
export const traverseFields = ({
|
||||
adapter,
|
||||
arrays,
|
||||
blocks,
|
||||
columnPrefix,
|
||||
@@ -92,7 +89,6 @@ export const traverseFields = ({
|
||||
if (field.localized) newRow.row._locale = locale;
|
||||
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays: newRow.arrays,
|
||||
blocks,
|
||||
columnPrefix: '',
|
||||
@@ -137,7 +133,6 @@ export const traverseFields = ({
|
||||
const blockTableName = `${newTableName}_${toSnakeCase(blockRow.blockType)}`;
|
||||
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays: newRow.arrays,
|
||||
blocks,
|
||||
columnPrefix: '',
|
||||
@@ -162,7 +157,6 @@ export const traverseFields = ({
|
||||
case 'group': {
|
||||
if (typeof data[field.name] === 'object' && data[field.name] !== null) {
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays,
|
||||
blocks,
|
||||
columnPrefix: `${columnName}_`,
|
||||
@@ -268,7 +262,7 @@ export const traverseFields = ({
|
||||
relationships.push(relationRow);
|
||||
} else {
|
||||
relationRow[`${field.relationTo}ID`] = relation;
|
||||
relationships.push(relationRow);
|
||||
if (relation) relationships.push(relationRow);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,34 +1,40 @@
|
||||
import { UpdateOne } from 'payload/dist/database/types';
|
||||
import toSnakeCase from 'to-snake-case';
|
||||
import { upsertRow } from '../upsertRow';
|
||||
import { SQL } from 'drizzle-orm';
|
||||
import buildQuery from '../queries/buildQuery';
|
||||
import { upsertRow } from '../upsertRow';
|
||||
|
||||
export const updateOne: UpdateOne = async function updateOne({
|
||||
collection: collectionSlug,
|
||||
data,
|
||||
draft,
|
||||
id,
|
||||
locale,
|
||||
req,
|
||||
where,
|
||||
draft,
|
||||
locale,
|
||||
}) {
|
||||
const collection = this.payload.collections[collectionSlug].config;
|
||||
|
||||
const query = await buildQuery({
|
||||
adapter: this,
|
||||
collectionSlug,
|
||||
locale,
|
||||
where,
|
||||
});
|
||||
let query: SQL<unknown>;
|
||||
|
||||
if (where) {
|
||||
query = await buildQuery({
|
||||
adapter: this,
|
||||
collectionSlug,
|
||||
locale,
|
||||
where,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await upsertRow({
|
||||
adapter: this,
|
||||
data,
|
||||
fallbackLocale: req.fallbackLocale,
|
||||
fields: collection.fields,
|
||||
id,
|
||||
locale: req.locale,
|
||||
operation: 'update',
|
||||
query,
|
||||
tableName: toSnakeCase(collectionSlug),
|
||||
where: query,
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { Field } from 'payload/types';
|
||||
import { SQL } from 'drizzle-orm';
|
||||
import { PostgresAdapter } from '../types';
|
||||
import { traverseFields } from './traverseFields';
|
||||
import { transform } from '../transform';
|
||||
import { BlockRowToInsert, RowToInsert } from './types';
|
||||
import { insertArrays } from './insertArrays';
|
||||
import { SQL, and, eq, inArray } from 'drizzle-orm';
|
||||
import { GenericColumn, PostgresAdapter } from '../types';
|
||||
import { transform } from '../transform/read';
|
||||
import { BlockRowToInsert } from '../transform/write/types';
|
||||
import { insertArrays } from '../insertArrays';
|
||||
import { transformForWrite } from '../transform/write';
|
||||
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
@@ -13,60 +13,55 @@ type Args = {
|
||||
fallbackLocale?: string | false
|
||||
fields: Field[]
|
||||
locale: string
|
||||
operation: 'create' | 'update'
|
||||
path?: string
|
||||
query?: SQL<unknown>
|
||||
tableName: string
|
||||
}
|
||||
upsertTarget?: GenericColumn
|
||||
} & ({
|
||||
where: SQL<unknown>
|
||||
id: never
|
||||
} | {
|
||||
id: string | number
|
||||
where: never
|
||||
})
|
||||
|
||||
export const upsertRow = async ({
|
||||
adapter,
|
||||
data,
|
||||
fallbackLocale,
|
||||
fields,
|
||||
id,
|
||||
locale,
|
||||
operation,
|
||||
path = '',
|
||||
query,
|
||||
tableName,
|
||||
upsertTarget,
|
||||
where,
|
||||
}: Args): Promise<Record<string, unknown>> => {
|
||||
// Split out the incoming data into the corresponding:
|
||||
// base row, locales, relationships, blocks, and arrays
|
||||
const rowToInsert: RowToInsert = {
|
||||
row: {},
|
||||
locale: {},
|
||||
relationships: [],
|
||||
blocks: {},
|
||||
arrays: {},
|
||||
};
|
||||
|
||||
// This function is responsible for building up the
|
||||
// above rowToInsert
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays: rowToInsert.arrays,
|
||||
blocks: rowToInsert.blocks,
|
||||
columnPrefix: '',
|
||||
const rowToInsert = transformForWrite({
|
||||
data,
|
||||
fields,
|
||||
locale,
|
||||
localeRow: rowToInsert.locale,
|
||||
newTableName: tableName,
|
||||
parentTableName: tableName,
|
||||
path,
|
||||
relationships: rowToInsert.relationships,
|
||||
row: rowToInsert.row,
|
||||
tableName,
|
||||
});
|
||||
|
||||
const target = upsertTarget || adapter.tables[tableName].id;
|
||||
|
||||
// First, we insert the main row
|
||||
let insertedRow: Record<string, unknown>;
|
||||
|
||||
// First, we insert / update the main row
|
||||
if (operation === 'create') {
|
||||
if (id) {
|
||||
rowToInsert.row.id = id;
|
||||
[insertedRow] = await adapter.db.insert(adapter.tables[tableName])
|
||||
.values(rowToInsert.row).returning();
|
||||
.values(rowToInsert.row)
|
||||
.onConflictDoUpdate({ target, set: rowToInsert.row })
|
||||
.returning();
|
||||
} else {
|
||||
[insertedRow] = await adapter.db.update(adapter.tables[tableName])
|
||||
.set(rowToInsert.row).where(query).returning();
|
||||
[insertedRow] = await adapter.db.insert(adapter.tables[tableName])
|
||||
.values(rowToInsert.row)
|
||||
.onConflictDoUpdate({ target, set: rowToInsert.row, where })
|
||||
.returning();
|
||||
}
|
||||
|
||||
let localeToInsert: Record<string, unknown>;
|
||||
@@ -102,28 +97,21 @@ export const upsertRow = async ({
|
||||
});
|
||||
});
|
||||
|
||||
if (operation === 'update') {
|
||||
// TODO: delete existing data for paths that are updated
|
||||
// 1. Arrays, and all sub-arrays from that point on including all locales
|
||||
// 2. Blocks by path including locales, and sub arrays
|
||||
// 3. Relationships by path
|
||||
}
|
||||
|
||||
// //////////////////////////////////
|
||||
// INSERT LOCALES
|
||||
// //////////////////////////////////
|
||||
|
||||
let insertedLocaleRow;
|
||||
let insertedLocaleRow: Record<string, unknown>;
|
||||
|
||||
if (localeToInsert) {
|
||||
promises.push(async () => {
|
||||
if (operation === 'create') {
|
||||
[insertedLocaleRow] = await adapter.db.insert(adapter.tables[`${tableName}_locales`])
|
||||
.values(localeToInsert).returning();
|
||||
} else {
|
||||
// TODO: QUERY for existing locale by parent ID, to get its ID
|
||||
// and upsert if it doesn't exist already
|
||||
}
|
||||
[insertedLocaleRow] = await adapter.db.insert(adapter.tables[`${tableName}_locales`])
|
||||
.values(localeToInsert)
|
||||
.onConflictDoUpdate({
|
||||
target: [adapter.tables[`${tableName}_locales`]._locale, adapter.tables[`${tableName}_locales`]._parentID],
|
||||
set: localeToInsert,
|
||||
})
|
||||
.returning();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -135,6 +123,42 @@ export const upsertRow = async ({
|
||||
|
||||
if (relationsToInsert.length > 0) {
|
||||
promises.push(async () => {
|
||||
// Delete any relationship rows for parent ID and paths that have been updated
|
||||
// prior to recreating them
|
||||
const localizedPathsToDelete = new Set<string>();
|
||||
const pathsToDelete = new Set<string>();
|
||||
|
||||
relationsToInsert.forEach((relation) => {
|
||||
if (typeof relation.path === 'string') {
|
||||
if (typeof relation.locale === 'string') {
|
||||
localizedPathsToDelete.add(relation.path);
|
||||
} else {
|
||||
pathsToDelete.add(relation.path);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (localizedPathsToDelete.size > 0) {
|
||||
await adapter.db.delete(adapter.tables[`${tableName}_relationships`])
|
||||
.where(
|
||||
and(
|
||||
eq(adapter.tables[`${tableName}_relationships`].parent, insertedRow.id),
|
||||
inArray(adapter.tables[`${tableName}_relationships`].path, [localizedPathsToDelete]),
|
||||
eq(adapter.tables[`${tableName}_relationships`].locale, locale),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (pathsToDelete.size > 0) {
|
||||
await adapter.db.delete(adapter.tables[`${tableName}_relationships`])
|
||||
.where(
|
||||
and(
|
||||
eq(adapter.tables[`${tableName}_relationships`].parent, insertedRow.id),
|
||||
inArray(adapter.tables[`${tableName}_relationships`].path, Array.from(pathsToDelete)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
insertedRelationshipRows = await adapter.db.insert(adapter.tables[`${tableName}_relationships`])
|
||||
.values(relationsToInsert).returning();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user