Last active
August 16, 2022 18:29
-
-
Save benjie/2db7bdfa9578660bdef6b35cc3485c42 to your computer and use it in GitHub Desktop.
Revisions
-
benjie revised this gist
Apr 29, 2021 . 1 changed file with 3 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -87,6 +87,9 @@ module.exports = function PgUpsertPlugin( null ); const TablePatch = getTypeByName(inflection.patchType(tableTypeName)); if (!TableInput || !TablePatch) { return {}; } const PayloadType = newWithHooks( GraphQLObjectType, { -
benjie revised this gist
Jul 9, 2019 . 1 changed file with 11 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,11 @@ Clone this plugin into your codebase. Add the plugin to PostGraphile via `--append-plugins` (CLI) or `appendPlugins: [...]`. Add a comment on the relevant constraint and/or table to enable upsert: ```sql comment on table my_table is E'@upsert'; -- or comment on constraint my_table_pkey of table my_tabls is E'@upsert'; ``` -
benjie created this gist
Mar 5, 2019 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,324 @@ module.exports = function PgUpsertPlugin( builder, { pgDisableDefaultMutations } ) { if (pgDisableDefaultMutations) { return; } builder.hook("inflection", (inflection, build) => build.extend(inflection, { upsertPayloadType(table) { return this.upperCamelCase( `upsert-${this._singularizedTableName(table)}-payload` ); }, upsertByConstraint(constraint) { if (constraint.tags.upsertFieldName) { return constraint.tags.upsertFieldName; } return this.camelCase( `upsert-${this._singularizedTableName( constraint.class )}-by-${constraint.keyAttributes .map(key => this.column(key)) .join("-and-")}` ); }, upsertByConstraintInputType(constraint) { return this.camelCase(`${this.upsertByConstraint(constraint)}-input`); }, }) ); builder.hook("GraphQLObjectType:fields", (fields, build, context) => { const { newWithHooks, extend, parseResolveInfo, getTypeByName, gql2pg, pgGetGqlTypeByTypeIdAndModifier, pgGetGqlInputTypeByTypeIdAndModifier, pgIntrospectionResultsByKind: introspectionResultsByKind, pgSql: sql, graphql: { GraphQLNonNull, GraphQLInputObjectType, GraphQLString, GraphQLObjectType, }, inflection, pgQueryFromResolveData: queryFromResolveData, pgOmit: omit, pgViaTemporaryTable: viaTemporaryTable, describePgEntity, pgField, } = build; const { scope: { isRootMutation }, fieldWithHooks, } = context; if (!isRootMutation) { return fields; } function upsertFieldsForTable(table) { if ( !table.namespace || !table.isUpdatable || !table.isInsertable || omit(table, "upsert") ) { return {}; } const TableType = pgGetGqlTypeByTypeIdAndModifier(table.type.id, null); if (!TableType) { return {}; } const uniqueConstraints = table.constraints.filter( con => con.type === "u" || con.type === "p" ); const tableTypeName = TableType.name; const TableInput = pgGetGqlInputTypeByTypeIdAndModifier( table.type.id, null ); const TablePatch = getTypeByName(inflection.patchType(tableTypeName)); const PayloadType = newWithHooks( GraphQLObjectType, { name: inflection.upsertPayloadType(table), description: `The output of our upsert \`${tableTypeName}\` mutation.`, // eslint-disable-next-line no-shadow fields: ({ fieldWithHooks }) => { const tableName = inflection.tableFieldName(table); return Object.assign({ clientMutationId: { description: "The exact same `clientMutationId` that was provided in the mutation input, unchanged and unused. May be used by a client to track mutations.", type: GraphQLString, }, [tableName]: pgField( build, fieldWithHooks, tableName, { description: `The \`${tableTypeName}\` that was upserted by this mutation.`, type: TableType, }, {}, false ), }); }, }, { __origin: `Adding table upsert mutation payload type for ${describePgEntity( table )}.`, isMutationPayload: true, isPgUpsertPayloadType: true, pgIntrospection: table, } ); return uniqueConstraints.reduce((memo, constraint) => { if (omit(constraint, "upsert")) return memo; if (!constraint.tags.upsert && !table.tags.upsert) return memo; const keys = constraint.keyAttributes; if (keys.some(key => omit(key, "read"))) { return memo; } const fieldName = inflection.upsertByConstraint(constraint); const InputType = newWithHooks( GraphQLInputObjectType, { description: `All input for the \`${fieldName}\` mutation.`, name: inflection.upsertByConstraintInputType(constraint), fields: Object.assign({ clientMutationId: { type: GraphQLString, }, [inflection.tableFieldName(table)]: { description: `The \`${tableTypeName}\` to be created by this mutation.`, type: new GraphQLNonNull(TableInput), }, [inflection.patchField(inflection.tableFieldName(table))]: { description: `An object where the defined keys will be set on the \`${tableTypeName}\` being updated.`, type: new GraphQLNonNull(TablePatch), }, }), }, { __origin: `Adding table upsert mutation input type for ${describePgEntity( constraint )}.`, isPgUpsertByKeysInputType: true, pgIntrospection: table, pgFieldIntrospection: constraint, pgKeys: keys, isMutationInput: true, } ); return extend( memo, { [fieldName]: fieldWithHooks( fieldName, fieldContext => { const { getDataFromParsedResolveInfoFragment } = fieldContext; return { description: `Inserts a single \`${tableTypeName}\`; if the record already exists then insteads updates the record.`, type: PayloadType, args: { input: { type: new GraphQLNonNull(InputType), }, }, async resolve(parent, args, resolveContext, resolveInfo) { const { input } = args; const { pgClient } = resolveContext; const parsedResolveInfoFragment = parseResolveInfo( resolveInfo ); parsedResolveInfoFragment.args = args; // Allow overriding via makeWrapResolversPlugin const resolveData = getDataFromParsedResolveInfoFragment( parsedResolveInfoFragment, PayloadType ); const sqlTypeIdentifier = sql.identifier( table.namespace.name, table.name ); const inputCreate = input[inflection.tableFieldName(table)]; const inputPatch = input[ inflection.patchField(inflection.tableFieldName(table)) ]; const columnsToInsert = []; const valuesToInsert = []; const updateStatements = []; table.attributes.forEach(attr => { const attrFieldName = inflection.column(attr); if (!omit(attr, "insert")) { if (attrFieldName in inputCreate) { columnsToInsert.push(sql.identifier(attr.name)); valuesToInsert.push( gql2pg( inputCreate[attrFieldName], attr.type, attr.typeModifier ) ); } } if (!omit(attr, "update")) { if (attrFieldName in inputPatch) { updateStatements.push( sql.fragment`${sql.identifier( attr.name )} = ${gql2pg( inputPatch[attrFieldName], attr.type, attr.typeModifier )}` ); } } }); const sqlMutationQuery = sql.query` insert into ${sql.identifier( table.namespace.name, table.name )} (${sql.join(columnsToInsert, ", ")}) values(${sql.join(valuesToInsert, ", ")}) on conflict (${sql.join( keys.map(key => sql.identifier(key.name)), ", " )}) do ${ updateStatements.length === 0 ? sql.fragment`nothing` : sql.fragment`update set ${sql.join(updateStatements, ", ")} returning *` }`; const modifiedRowAlias = sql.identifier( Symbol("modified_row") ); const query = queryFromResolveData( modifiedRowAlias, modifiedRowAlias, resolveData, {}, null, resolveContext ); let row; try { await pgClient.query("SAVEPOINT graphql_mutation"); const rows = await viaTemporaryTable( pgClient, sqlTypeIdentifier, sqlMutationQuery, modifiedRowAlias, query ); row = rows[0]; await pgClient.query( "RELEASE SAVEPOINT graphql_mutation" ); } catch (e) { await pgClient.query( "ROLLBACK TO SAVEPOINT graphql_mutation" ); throw e; } if (!row) { throw new Error( `Could not upsert that record - check your permissions` ); } return { clientMutationId: input.clientMutationId, data: row, }; }, }; }, { isPgNodeMutation: false, pgFieldIntrospection: table, pgFieldConstraint: constraint, isPgUpsertMutationField: true, } ), }, `Adding upsert mutation for ${describePgEntity(constraint)}` ); }, {}); } return extend( fields, introspectionResultsByKind.class.reduce( (memo, table) => extend( memo, upsertFieldsForTable(table), `Adding upsert mutations for ${describePgEntity(table)}` ), {} ), `Adding upsert mutations to root Mutation type` ); }); };