diff --git a/docs/graphql/core/databases/ms-sql-server/index.rst b/docs/graphql/core/databases/ms-sql-server/index.rst index 9d750526c90..c0bca80e7c8 100644 --- a/docs/graphql/core/databases/ms-sql-server/index.rst +++ b/docs/graphql/core/databases/ms-sql-server/index.rst @@ -35,12 +35,12 @@ Here are 2 ways you can get started with Hasura and SQL Server: Supported features ------------------ -Hasura currently supports queries, subscriptions, relationships and permissions on MS SQL Server. +Hasura currently supports queries, subscriptions, inserts, deletes, relationships and permissions on MS SQL Server. Next up on our roadmap for Hasura + SQL Server: - Support for stored procedures & functions (`#7073 `__) -- Mutations: Run inserts, updates, stored procedures and transactions securely on SQL Server over a GraphQL API (`#7074 `__) +- Mutations: Run updates, stored procedures and transactions securely on SQL Server over a GraphQL API (`#7074 `__) - Event triggers: Trigger HTTP webhooks with atomic capture and atleast once guarantee whenever data changes inside the database (`#7075 `__) - Remote Joins: Join models in SQL Server to models from other API services (GraphQL or REST) (`#7076 `__) @@ -75,8 +75,4 @@ Know more :maxdepth: 1 :titlesonly: - Schema - Queries - Mutations - Subscriptions Supported MS SQL Server types diff --git a/docs/graphql/core/databases/ms-sql-server/mutations/index.rst b/docs/graphql/core/databases/ms-sql-server/mutations/index.rst index 287645f8a7f..483f9540566 100644 --- a/docs/graphql/core/databases/ms-sql-server/mutations/index.rst +++ b/docs/graphql/core/databases/ms-sql-server/mutations/index.rst @@ -31,14 +31,20 @@ The following types of mutation requests are possible: .. toctree:: :maxdepth: 1 + Insert + Upsert Delete + +.. note:: + + Support for the update mutation will be added soon. + + .. TODO: DBCOMPATIBILITY .. toctree:: :maxdepth: 1 - Insert - Upsert Update multiple-mutations diff --git a/docs/graphql/core/databases/ms-sql-server/mutations/insert.rst b/docs/graphql/core/databases/ms-sql-server/mutations/insert.rst new file mode 100644 index 00000000000..2006a43b10d --- /dev/null +++ b/docs/graphql/core/databases/ms-sql-server/mutations/insert.rst @@ -0,0 +1,380 @@ +.. meta:: + :description: Insert an object into MS SQL Server using a mutation + :keywords: hasura, docs, ms sql server, mutation, insert + +.. _ms_sql_server_insert: + +MS SQL Server: Insert mutation +============================== + +.. contents:: Table of contents + :backlinks: none + :depth: 1 + :local: + +Auto-generated insert mutation schema +------------------------------------- + +**For example**, the auto-generated schema for the insert mutation field for a table ``article`` looks like the following: + +.. code-block:: graphql + + insert_article ( + objects: [article_insert_input!]! + if_matched: article_if_matched + ): article_mutation_response + + # response of any mutation on the table "article" + type article_mutation_response { + # number of affected rows by the mutation + affected_rows: Int! + # data of the affected rows by the mutation + returning: [article!]! + } + + # single object insert + insert_article_one ( + object: article_insert_input! + if_matched: article_if_matched + ): article + +As you can see from the schema: + +- ``objects`` argument is mandatory and you can pass multiple ``objects`` to the mutation. +- You can pass an ``if_matched`` argument to convert the mutation to an :ref:`upsert mutation `. +- You can return the number of affected rows and the affected objects (with nested objects) in the response. +- You can use the single object insert to get the inserted object directly as the mutation response. + +.. + See the :ref:`insert mutation API reference ` for the full specifications. + +.. note:: + + If a table is not in the ``dbo`` MS SQL Server schema, the insert mutation field will be of the format + ``insert__``. + +Insert a single object +---------------------- + +**Example:** Insert a new ``article`` object and return the inserted article object in the response: + +.. graphiql:: + :view_only: + :query: + mutation insert_single_article { + insert_article_one( + object: { + title: "Article 1", + content: "Sample article content", + author_id: 3 + } + ) { + id + title + } + } + :response: + { + "data": { + "insert_article_one": { + "id": 21, + "title": "Article 1" + } + } + } + +Using variables: + +.. graphiql:: + :view_only: + :query: + mutation insert_single_article($object: article_insert_input! ) { + insert_article_one(object: $object) { + id + title + } + } + :response: + { + "data": { + "insert_article_one": { + "id": 21, + "title": "Article 1" + } + } + } + :variables: + { + "object": { + "title": "Article 1", + "content": "Sample article content", + "author_id": 3 + } + } + +.. note:: + + The ``insert__one`` mutation will **only** be available if you have select permissions on the table, as it returns the inserted row. + +Insert multiple objects of the same type in the same mutation +------------------------------------------------------------- +**Example:** Insert 2 new ``article`` objects and return both the article objects in the response: + +.. graphiql:: + :view_only: + :query: + mutation insert_multiple_articles { + insert_article( + objects: [ + { + title: "Article 2", + content: "Sample article content", + author_id: 4 + }, + { + title: "Article 3", + content: "Sample article content", + author_id: 5 + } + ] + ) { + returning { + id + title + } + } + } + :response: + { + "data": { + "insert_article": { + "affected_rows": 2, + "returning": [ + { + "id": 22, + "title": "Article 2" + }, + { + "id": 23, + "title": "Article 3" + } + ] + } + } + } + +Using variables: + +.. graphiql:: + :view_only: + :query: + mutation insert_multiple_articles($objects: [article_insert_input!]! ) { + insert_article(objects: $objects) { + returning { + id + title + } + } + } + :response: + { + "data": { + "insert_article": { + "affected_rows": 2, + "returning": [ + { + "id": 22, + "title": "Article 2" + }, + { + "id": 23, + "title": "Article 3" + } + ] + } + } + } + :variables: + { + "objects": [ + { + "title": "Article 2", + "content": "Sample article content", + "author_id": 4 + }, + { + "title": "Article 3", + "content": "Sample article content", + "author_id": 5 + } + ] + } + + +Insert an object and get a nested object in response +---------------------------------------------------- + +**Example:** Insert a new ``article`` object and return the inserted article object with its author in the response: + +.. graphiql:: + :view_only: + :query: + mutation insert_article { + insert_article( + objects: [ + { + title: "Article 1", + content: "Sample article content", + author_id: 3 + } + ] + ) { + returning { + id + title + author { + id + name + } + } + } + } + :response: + { + "data": { + "insert_article": { + "affected_rows": 1, + "returning": [ + { + "id": 21, + "title": "Article 1", + "author": { + "id": 3, + "name": "Sidney" + } + } + ] + } + } + } + + +Set a field to its default value during insert +---------------------------------------------- + +To set a field to its ``default`` value, just omit it from the input object, irrespective of the +default value configuration i.e. via MS SQL Server defaults or using column presets. + +**Example:** If the default value of ``id`` is set to auto-incrementing integer, there's no need to pass the ``id`` field to the input object: + +.. graphiql:: + :view_only: + :query: + mutation insert_article_with_def_id { + insert_article( + objects: [ + { + title: "Article 1", + content: "Sample article content", + author_id: 3 + } + ] + ) { + returning { + id + title + } + } + } + :response: + { + "data": { + "insert_article": { + "affected_rows": 1, + "returning": [ + { + "id": 21, + "title": "Article 1" + } + ] + } + } + } + +Set a field to NULL during insert +--------------------------------- + +If a field is ``nullable`` in the database, to set its value to ``null``, either pass its value as ``null`` or +just omit it from the input object. + +**Example:** If ``age`` is a nullable field, to set it to ``null``, either don't pass the age field to the input object +or pass it as ``null``: + +.. graphiql:: + :view_only: + :query: + mutation insert_author_with_null_age { + insert_author( + objects: [ + { + name: "Jeff" + } + ] + ) { + returning { + id + name + age + } + } + } + :response: + { + "data": { + "insert_author": { + "returning": [ + { + "id": 11, + "name": "Jeff", + "age": null + } + ] + } + } + } + +OR + +.. graphiql:: + :view_only: + :query: + mutation insert_author_with_null_age { + insert_author( + objects: [ + { + name: "Jeff", + age: null + } + ] + ) { + returning { + id + name + age + } + } + } + :response: + { + "data": { + "insert_author": { + "returning": [ + { + "id": 11, + "name": "Jeff", + "age": null + } + ] + } + } + } diff --git a/docs/graphql/core/databases/ms-sql-server/mutations/upsert.rst b/docs/graphql/core/databases/ms-sql-server/mutations/upsert.rst new file mode 100644 index 00000000000..3f805bea257 --- /dev/null +++ b/docs/graphql/core/databases/ms-sql-server/mutations/upsert.rst @@ -0,0 +1,244 @@ +.. meta:: + :description: Use upsert mutations on MS SQL Server with Hasura + :keywords: hasura, docs, ms sql server, mutation, upsert + +.. _ms_sql_server_upsert: + +MS SQL Server: Upsert mutation +============================== + +.. contents:: Table of contents + :backlinks: none + :depth: 1 + :local: + +Introduction +------------ + +An upsert query ensures the given set of rows are present in the database, inserting new rows or updating existing +rows as necessary, subject to certain criteria. + + +Convert insert mutation to upsert +--------------------------------- + +.. note:: + + Only tables with **update** permissions are **upsertable**. i.e. a table's update permissions are respected + before updating an existing row in case of a match. + +To convert an :ref:`insert mutation ` into an upsert, you need to use the ``if_matched`` argument to specify: + +- the **columns to be matched** for each row using the ``match_columns`` field. +- the **columns to be updated** in the case of a match using the ``update_columns`` field. +- a **condition** for updating the column using the ``where`` field, and + +The value of the ``update_columns`` field determines the behaviour of the upsert request as shown via the use cases +below. + +Upsert is not a substitute for update +------------------------------------- + +The upsert functionality is sometimes confused with the update functionality. However, they work slightly +differently. An upsert mutation is used in the case when it's not clear if the respective row is already present +in the database. If it's known that the row is present in the database, ``update`` is the functionality to use. + +For an upsert, **all columns that are necessary for an insert are required**. + +**How it works** + +1. MS SQL Server tries to insert a row (hence all the required columns need to be present) + +2. If this fails because of some match, it updates the specified columns + +If not all required columns are present, an error like ``NULL value unexpected for `` can occur. + + +Update selected columns on match +-------------------------------- + +The ``update_columns`` field can be used to specify which columns to update in case a match occurs. + +**Example**: Insert a new object in the ``article`` table or, if the value of the ``title`` column +matches a a ``title`` value in an existing row, update the ``content`` column of the existing article: + +.. graphiql:: + :view_only: + :query: + mutation upsert_article { + insert_article ( + objects: [ + { + title: "Article 1", + content: "Updated article 1 content", + published_on: "2018-10-12" + } + ], + if_matched: { + match_columns: title, + update_columns: content + } + ) { + returning { + id + title + content + published_on + } + } + } + :response: + { + "data": { + "insert_article": { + "returning": [ + { + "id": 1, + "title": "Article 1", + "content": "Updated article 1 content", + "published_on": "2018-06-15" + } + ] + } + } + } + +Note that the ``published_on`` column is left unchanged as it wasn't present in ``update_columns``. + +.. note:: + + If ``match_columns`` is an **empty array** there is no basis for comparing new rows to existing rows. + Thus the mutation will always **insert** the values and will never update any rows. + + **Example**: Insert a new object into the article table, will not match on columns because + none are specified. + + .. graphiql:: + :view_only: + :query: + mutation upsert_article { + insert_article ( + objects: [ + { + title: "Article 1", + content: "Article 1 content", + published_on: "2018-10-12" + } + ], + if_matched: { + match_columns: [], + update_columns: content + } + ) { + returning { + id + title + content + published_on + } + } + } + :response: + { + "data": { + "insert_article": { + "returning": [ + { + "id": 3, + "title": "Article 1", + "content": "Article 1 content", + "published_on": "2018-06-15" + } + ] + } + } + } + + This query is equivalent to a simple insert without an ``if_matched`` clause. + + +Update selected columns on match subject to a filter +---------------------------------------------------- + +A ``where`` condition can be added to the ``if_matched`` clause to check a condition before making the update in case a +match occurs. + +**Example**: Insert a new object in the ``article`` table, or if the value of the ``title`` column +matches a a ``title`` value in an existing row, update the ``published_on`` column specified +in ``update_columns`` only if the previous ``published_on`` +value is lesser than the new value: + +.. graphiql:: + :view_only: + :query: + mutation upsert_article { + insert_article ( + objects: [ + { + title: "Article 2", + published_on: "2018-10-12" + } + ], + if_matched: { + match_columns: title, + update_columns: published_on, + where: { + published_on: {_lt: "2018-10-12"} + } + } + ) { + returning { + id + title + published_on + } + } + } + :response: + { + "data": { + "insert_article": { + "returning": [ + { + "id": 2, + "title": "Article 2", + "published_on": "2018-10-12" + } + ] + } + } + } + +Ignore request on match +----------------------- + +If ``update_columns`` is an **empty array** then on match the changes are ignored. + +**Example**: Insert a new author object into the author table, unless there already exists an author with the same name. + +.. graphiql:: + :view_only: + :query: + mutation upsert_author { + insert_author( + objects: [ + { name: "John" } + ], + if_matched: { + match_columns: name, + update_columns: [] + } + ) { + affected_rows + } + } + :response: + { + "data": { + "insert_author": { + "affected_rows": 0 + } + } + } + +In this case, the insert mutation is ignored because there is a match and ``update_columns`` is empty. diff --git a/docs/graphql/core/databases/postgres/mutations/upsert.rst b/docs/graphql/core/databases/postgres/mutations/upsert.rst index b90ade4a4dd..4f8bb16a076 100644 --- a/docs/graphql/core/databases/postgres/mutations/upsert.rst +++ b/docs/graphql/core/databases/postgres/mutations/upsert.rst @@ -11,7 +11,7 @@ Postgres: Upsert mutation :backlinks: none :depth: 1 :local: - + Introduction ------------ @@ -69,7 +69,7 @@ Update selected columns on conflict The ``update_columns`` field can be used to specify which columns to update in case a conflict occurs. -**Example**: Insert a new object in the ``article`` table or, if the unique constraint ``article_title_key`` is +**Example**: Insert a new object in the ``article`` table or, if the unique constraint ``article_title_key`` is violated, update the ``content`` column of the existing article: .. graphiql:: @@ -80,7 +80,7 @@ violated, update the ``content`` column of the existing article: objects: [ { title: "Article 1", - content: "Article 1 content", + content: "Updated article 1 content", published_on: "2018-10-12" } ], @@ -105,7 +105,7 @@ violated, update the ``content`` column of the existing article: { "id": 1, "title": "Article 1", - "content": "Article 1 content", + "content": "Updated article 1 content", "published_on": "2018-06-15" } ] @@ -118,11 +118,11 @@ Note that the ``published_on`` column is left unchanged as it wasn't present in Update selected columns on conflict using a filter -------------------------------------------------- -A ``where`` condition can be added to the ``on_conflict`` clause to check a condition before making the update in case a +A ``where`` condition can be added to the ``on_conflict`` clause to check a condition before making the update in case a conflict occurs **Example**: Insert a new object in the ``article`` table, or if the unique key constraint ``article_title_key`` is -violated, update the ``published_on`` columns specified in ``update_columns`` only if the previous ``published_on`` +violated, update the ``published_on`` column specified in ``update_columns`` only if the previous ``published_on`` value is lesser than the new value: .. graphiql:: @@ -168,9 +168,9 @@ value is lesser than the new value: Ignore request on conflict -------------------------- -If ``update_columns`` is an **empty array** then on conflict the changes are ignored. +If ``update_columns`` is an **empty array** then on conflict the changes are ignored. -**Example**: Insert a new object into the author table or, if the unique constraint ``author_name_key`` is violated, +**Example**: Insert a new object into the author table or, if the unique constraint ``author_name_key`` is violated, ignore the request. .. graphiql:: @@ -205,7 +205,7 @@ Upsert in nested mutations -------------------------- You can specify the ``on_conflict`` clause while inserting nested objects: -**Example**: +**Example**: .. graphiql:: :view_only: