diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000..ea2a54fe81339 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +* @shahidhk +/server/ @0x777 +/docs/ @rikinsk +/cli/ @shahidhk +/console/ @praveenweb diff --git a/console/src/components/Services/Data/Function/Modify/ModifyCustomFunction.js b/console/src/components/Services/Data/Function/Modify/ModifyCustomFunction.js index 6a071106ecbdb..99cc491822b38 100644 --- a/console/src/components/Services/Data/Function/Modify/ModifyCustomFunction.js +++ b/console/src/components/Services/Data/Function/Modify/ModifyCustomFunction.js @@ -31,13 +31,21 @@ class ModifyCustomFunction extends React.Component { } componentDidMount() { const { functionName, schema } = this.props.params; - if (!functionName) { + if (!functionName || !schema) { this.props.dispatch(push(prefixUrl)); } Promise.all([ this.props.dispatch(fetchCustomFunction(functionName, schema)), ]); } + componentWillReceiveProps(nextProps) { + const { functionName, schema } = this.props.params; + if (functionName !== nextProps.params.functionName || schema !== nextProps.params.schema) { + Promise.all([ + this.props.dispatch(fetchCustomFunction(nextProps.params.functionName, nextProps.params.schema)), + ]); + } + } loadRunSQLAndLoadPage() { const { functionDefinition } = this.props.functions; Promise.all([ diff --git a/console/src/components/Services/Data/Function/Permission/Permission.js b/console/src/components/Services/Data/Function/Permission/Permission.js index 2c4ee9fbf3a09..de9f6c490eabf 100644 --- a/console/src/components/Services/Data/Function/Permission/Permission.js +++ b/console/src/components/Services/Data/Function/Permission/Permission.js @@ -2,7 +2,7 @@ import React from 'react'; import Helmet from 'react-helmet'; import CommonTabLayout from '../../../Layout/CommonTabLayout/CommonTabLayout'; -import { Link } from 'react-router'; +// import { Link } from 'react-router'; import { push } from 'react-router-redux'; import { pageTitle, appPrefix } from '../Modify/constants'; @@ -16,11 +16,13 @@ import { fetchCustomFunction } from '../customFunctionReducer'; class Permission extends React.Component { componentDidMount() { - const { functionName } = this.props.params; + const { functionName, schema } = this.props.params; if (!functionName) { this.props.dispatch(push(prefixUrl)); } - Promise.all([this.props.dispatch(fetchCustomFunction(functionName))]); + Promise.all([ + this.props.dispatch(fetchCustomFunction(functionName, schema)), + ]); } render() { const styles = require('../Modify/ModifyCustomFunction.scss'); @@ -28,9 +30,11 @@ class Permission extends React.Component { functionSchema: schema, functionName, setOffTable, + setOffTableSchema, } = this.props.functions; + const baseUrl = `${appPrefix}/schema/${schema}/functions/${functionName}`; - const permissionTableUrl = `${appPrefix}/schema/${schema}/tables/${setOffTable}/permissions`; + const permissionTableUrl = `${prefixUrl}/schema/${setOffTableSchema}/tables/${setOffTable}/permissions`; const breadCrumbs = [ { @@ -78,14 +82,14 @@ class Permission extends React.Component { applicable to the data returned by this function

- + - +
); diff --git a/console/src/components/Services/Data/Function/customFunctionReducer.js b/console/src/components/Services/Data/Function/customFunctionReducer.js index ef6e89d493643..9fcad1f1eecd2 100644 --- a/console/src/components/Services/Data/Function/customFunctionReducer.js +++ b/console/src/components/Services/Data/Function/customFunctionReducer.js @@ -170,17 +170,25 @@ const fetchCustomFunction = (functionName, schema) => { const deleteFunctionSql = () => { return (dispatch, getState) => { const currentSchema = getState().tables.currentSchema; - const functionName = getState().functions.functionName; - const functionDefinition = getState().functions.functionDefinition; - const sqlDropFunction = - 'DROP FUNCTION ' + - '"' + - currentSchema + - '"' + - '.' + - '"' + - functionName + - '"'; + const { + functionName, + functionDefinition, + inputArgTypes, + } = getState().functions; + let functionWSchemaName = + '"' + currentSchema + '"' + '.' + '"' + functionName + '"'; + + if (inputArgTypes.length > 0) { + let functionString = '('; + inputArgTypes.forEach((i, index) => { + functionString += + i + ' ' + (index === inputArgTypes.length - 1 ? ')' : ','); + }); + functionWSchemaName += functionString; + } + + const sqlDropFunction = 'DROP FUNCTION ' + functionWSchemaName; + const sqlUpQueries = [ { type: 'run_sql', @@ -203,9 +211,11 @@ const deleteFunctionSql = () => { const errorMsg = 'Deleting function failed'; const customOnSuccess = () => { - dispatch(_push('/')); + dispatch(_push(`/schema/${currentSchema}`)); + }; + const customOnError = () => { + dispatch({ type: DELETE_CUSTOM_FUNCTION_FAIL }); }; - const customOnError = () => {}; dispatch({ type: DELETING_CUSTOM_FUNCTION }); return dispatch( @@ -318,6 +328,9 @@ const customFunctionReducer = (state = functionData, action) => { functionSchema: action.data[0][0].function_schema || null, functionDefinition: action.data[1][0].function_definition || null, setOffTable: action.data[1][0].return_type_name || null, + setOffTableSchema: action.data[1][0].return_type_schema || null, + inputArgNames: action.data[1][0].input_arg_names || null, + inputArgTypes: action.data[1][0].input_arg_types || null, isFetching: false, isFetchError: null, }; diff --git a/console/src/components/Services/Data/Function/customFunctionState.js b/console/src/components/Services/Data/Function/customFunctionState.js index 7ee95fbe7e999..4be014fb5652e 100644 --- a/console/src/components/Services/Data/Function/customFunctionState.js +++ b/console/src/components/Services/Data/Function/customFunctionState.js @@ -12,6 +12,9 @@ const functionData = { functionSchema: '', functionDefinition: '', setOffTable: '', + setOffTableSchema: '', + inputArgNames: [], + inputArgTypes: [], ...asyncState, }; diff --git a/console/src/components/Services/Data/TablePermissions/PermissionBuilder/PermissionBuilder.js b/console/src/components/Services/Data/TablePermissions/PermissionBuilder/PermissionBuilder.js index d60f93befa7f3..f513b38084062 100644 --- a/console/src/components/Services/Data/TablePermissions/PermissionBuilder/PermissionBuilder.js +++ b/console/src/components/Services/Data/TablePermissions/PermissionBuilder/PermissionBuilder.js @@ -71,10 +71,7 @@ class PermissionBuilder extends React.Component { } else if (isNotOperator(operator)) { const newPath = pathSplit.slice(1).join('.'); _missingSchemas = findMissingSchemas(newPath, currTable); - } else if ( - isColumnOperator(operator) || - isArrayColumnOperator(operator) - ) { + } else if (isColumnOperator(operator) || isArrayColumnOperator(operator)) { // no missing schemas } else { const { allTableSchemas } = this.props; diff --git a/console/src/components/Services/Data/TablePermissions/PermissionBuilder/utils.js b/console/src/components/Services/Data/TablePermissions/PermissionBuilder/utils.js index 841fd9c0dcbcf..a544d811be3bc 100644 --- a/console/src/components/Services/Data/TablePermissions/PermissionBuilder/utils.js +++ b/console/src/components/Services/Data/TablePermissions/PermissionBuilder/utils.js @@ -122,27 +122,30 @@ export function getTableDef(tableName, schema) { export function getRefTable(rel, tableSchema) { let _refTable = null; - if (rel.rel_type === 'array') { - if (rel.rel_def.foreign_key_constraint_on) { + // if manual relationship + if (rel.rel_def.manual_configuration) { + _refTable = rel.rel_def.manual_configuration.remote_table; + } + + // if foreign-key based relationship + if (rel.rel_def.foreign_key_constraint_on) { + // if array relationship + if (rel.rel_type === 'array') { _refTable = rel.rel_def.foreign_key_constraint_on.table; - } else if (rel.rel_def.manual_configuration) { - _refTable = rel.rel_def.manual_configuration.remote_table; } - } - if (rel.rel_type === 'object') { - if (rel.rel_def.foreign_key_constraint_on) { + // if object relationship + if (rel.rel_type === 'object') { const fkCol = rel.rel_def.foreign_key_constraint_on; for (let i = 0; i < tableSchema.foreign_key_constraints.length; i++) { const fkConstraint = tableSchema.foreign_key_constraints[i]; - if (fkCol === Object.keys(fkConstraint.column_mapping)[0]) { - _refTable = fkConstraint.ref_table; + const fkConstraintCol = Object.keys(fkConstraint.column_mapping)[0]; + if (fkCol === fkConstraintCol) { + _refTable = getTableDef(fkConstraint.ref_table, fkConstraint.ref_table_table_schema); break; } } - } else if (rel.rel_def.manual_configuration) { - _refTable = rel.rel_def.manual_configuration.remote_table; } } diff --git a/docs/_static/hasura-custom.css b/docs/_static/hasura-custom.css index 739de736754e3..51433cb73929c 100644 --- a/docs/_static/hasura-custom.css +++ b/docs/_static/hasura-custom.css @@ -131,6 +131,8 @@ ul { -webkit-box-shadow: -1px 2px 20px 2px rgba(217,217,217,1); -moz-box-shadow: -1px 2px 20px 2px rgba(217,217,217,1); box-shadow: -1px 2px 20px 2px rgba(217,217,217,1); + margin-top: 20px; + margin-bottom: 30px; } #docs-content img.no-shadow { diff --git a/docs/_theme/djangodocs/layout.html b/docs/_theme/djangodocs/layout.html index c67c73d1fa40c..dd9c73810aab4 100644 --- a/docs/_theme/djangodocs/layout.html +++ b/docs/_theme/djangodocs/layout.html @@ -134,7 +134,7 @@
check this page on github | contributing guidelines - | report an issue + | report an issue

diff --git a/docs/graphql/manual/api-reference/index.rst b/docs/graphql/manual/api-reference/index.rst index 2fcb9006332ef..905383a941bfb 100644 --- a/docs/graphql/manual/api-reference/index.rst +++ b/docs/graphql/manual/api-reference/index.rst @@ -18,13 +18,13 @@ Request types The following types of requests can be made using the GraphQL API: -- :doc:`Query/Subscription ` +- :doc:`Query / Subscription ` - :doc:`Mutation ` -Schema/Metadata API -------------------- +Schema / Metadata API +--------------------- -Hasura exposes a Schema/Metadata API for managing metadata for permissions/relationships or for directly +Hasura exposes a Schema / Metadata API for managing metadata for permissions/relationships or for directly executing SQL on the underlying Postgres. This is primarily intended to be used as an ``admin`` API to manage Hasura schema and metadata. @@ -36,7 +36,7 @@ Request types The following lists all the types of requests that can be made using the Schema/Metadata API: -- :ref:`Schema/Metadata API query types ` +- :ref:`Schema / Metadata API query types ` Supported PostgreSQL types -------------------------- @@ -48,7 +48,7 @@ You can refer to the following to know about all PostgreSQL types supported by t :maxdepth: 1 :hidden: - Query/Subscription + Query / Subscription Mutation - Schema/Metadata APIs + Schema / Metadata APIs Supported PostgreSQL types diff --git a/docs/graphql/manual/api-reference/mutation.rst b/docs/graphql/manual/api-reference/mutation.rst index d86fe192a6ec1..d348881eecd73 100644 --- a/docs/graphql/manual/api-reference/mutation.rst +++ b/docs/graphql/manual/api-reference/mutation.rst @@ -1,13 +1,13 @@ -.. title:: API Reference - Mutation - API Reference - Mutation ======================== .. contents:: Table of contents :backlinks: none - :depth: 1 + :depth: 2 :local: +.. _insert_upsert_syntax: + Insert/Upsert syntax -------------------- @@ -91,6 +91,7 @@ Insert/Upsert syntax } } +.. _update_syntax: Update syntax ------------- @@ -170,6 +171,8 @@ Update syntax } } +.. _delete_syntax: + Delete syntax ------------- @@ -229,10 +232,37 @@ Delete syntax Syntax definitions ------------------ +.. _MutationResponse: + +Mutation Response +^^^^^^^^^^^^^^^^^ +.. code-block:: none + + { + affected_rows + returning { + response-field1 + response-field2 + .. + } + } + +E.g.: + +.. code-block:: graphql + + { + affected_rows + returning { + id + author_id + } + } + .. _InputObject: -Input Object -^^^^^^^^^^^^ +**objects** argument +^^^^^^^^^^^^^^^^^^^^ .. code-block:: none @@ -271,37 +301,10 @@ E.g.: } ] -.. _MutationResponse: - -Mutation Response -^^^^^^^^^^^^^^^^^ -.. code-block:: none - - { - affected_rows - returning { - response-field1 - response-field2 - .. - } - } - -E.g.: - -.. code-block:: graphql - - { - affected_rows - returning { - id - author_id - } - } - .. _ConflictClause: -Conflict Clause -^^^^^^^^^^^^^^^ +**on_conflict** argument +^^^^^^^^^^^^^^^^^^^^^^^^ Conflict clause is used to convert an *insert* query to an *upsert* query. *Upsert* respects the table's *update* permissions before editing an existing row in case of a conflict. Hence the conflict clause is permitted only if a table has *update* permissions defined. @@ -324,7 +327,7 @@ E.g.: .. _whereArgExp: -``where`` argument +**where** argument ^^^^^^^^^^^^^^^^^^ .. parsed-literal:: @@ -377,7 +380,8 @@ ColumnExp Operator ######## -Generic operators (all column types except json, jsonb) : + +**Generic operators (all column types except json, jsonb):** - ``_eq`` - ``_ne`` @@ -388,7 +392,7 @@ Generic operators (all column types except json, jsonb) : - ``_gte`` - ``_lte`` -Operators for comparing columns (all column types except json, jsonb): +**Operators for comparing columns (all column types except json, jsonb)**: - ``_ceq`` - ``_cneq`` @@ -397,7 +401,7 @@ Operators for comparing columns (all column types except json, jsonb): - ``_cgte`` - ``_cnlte`` -Text related operators : +**Text related operators:** - ``_like`` - ``_nlike`` @@ -406,13 +410,13 @@ Text related operators : - ``_similar`` - ``_nsimilar`` -Checking for ``null`` values : +**Checking for NULL values:** - ``_is_null`` (takes true/false as values) .. _setArgExp: -``_set`` argument +**_set** argument ^^^^^^^^^^^^^^^^^ .. code-block:: none @@ -425,7 +429,7 @@ Checking for ``null`` values : .. _incArgExp: -``_inc`` argument +**_inc** argument ^^^^^^^^^^^^^^^^^ .. code-block:: none @@ -438,7 +442,7 @@ Checking for ``null`` values : .. _appendArgExp: -``_append`` argument +**_append** argument ^^^^^^^^^^^^^^^^^^^^ .. code-block:: none @@ -460,7 +464,7 @@ E.g. .. _prependArgExp: -``_prepend`` argument +**_prepend** argument ^^^^^^^^^^^^^^^^^^^^^ .. code-block:: none @@ -482,7 +486,7 @@ E.g. .. _deleteKeyArgExp: -``_delete_key`` argument +**_delete_key** argument ^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: none @@ -495,7 +499,7 @@ E.g. .. _deleteElemArgExp: -``_delete_elem`` argument +**_delete_elem** argument ^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: none @@ -508,7 +512,7 @@ E.g. .. _deleteAtPathArgExp: -``_delete_at_path`` argument +**_delete_at_path** argument ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: none diff --git a/docs/graphql/manual/api-reference/query.rst b/docs/graphql/manual/api-reference/query.rst index 53b83ac5a49a8..0d1c01ade8bd9 100644 --- a/docs/graphql/manual/api-reference/query.rst +++ b/docs/graphql/manual/api-reference/query.rst @@ -1,7 +1,5 @@ -.. title:: API Reference - Query/Subscription - -API Reference - Query/Subscription -================================== +API Reference - Query / Subscription +==================================== .. contents:: Table of contents :backlinks: none @@ -71,7 +69,11 @@ Syntax definitions Object ^^^^^^ -.. _simple_object: +.. parsed-literal:: + + SimpleObject_ | AggregateObject_ + +.. _SimpleObject: Simple Object ************* @@ -108,7 +110,7 @@ E.g. } } -.. _aggregate_object: +.. _AggregateObject: Aggregate Object **************** @@ -218,7 +220,7 @@ E.g. title } - article_aggregate{ # aggregate nested object + article_aggregate { # aggregate nested object aggregate { count } @@ -328,6 +330,19 @@ Operator - ``_gte`` - ``_lte`` +**Text related operators:** + +- ``_like`` +- ``_nlike`` +- ``_ilike`` +- ``_nilike`` +- ``_similar`` +- ``_nsimilar`` + +**Checking for NULL values:** + +- ``_is_null`` (takes true/false as values) + **JSONB operators:** .. list-table:: @@ -348,19 +363,6 @@ Operator (For more details on what these operators do, refer to `Postgres docs `__.) -**Text related operators :** - -- ``_like`` -- ``_nlike`` -- ``_ilike`` -- ``_nilike`` -- ``_similar`` -- ``_nsimilar`` - -**Checking for NULL values:** - -- ``_is_null`` (takes true/false as values) - **PostGIS related operators on GEOMETRY columns:** .. list-table:: @@ -389,10 +391,10 @@ Operator .. note:: - 1. All operators take a ``json`` representation of ``geometry/geography`` values. - 2. Input value for ``_st_d_within`` operator is an object:- + - All operators take a JSON representation of ``geometry/geography`` values as input value. + - Input value for ``_st_d_within`` operator is an object: - .. parsed-literal:: + .. parsed-literal:: { field-name : {_st_d_within: {distance: Float, from: Value} } @@ -428,7 +430,7 @@ or TableOrderBy -************ +"""""""""""" For columns: @@ -463,32 +465,8 @@ Order by type for "article" table: likes_aggregate: likes_aggregate_order_by } -AggregateOrderBy -**************** - -Count aggregate - -.. parsed-literal:: - {count: OrderByEnum_} - -Operation aggregate - -.. parsed-literal:: - {op_name: TableAggOpOrderBy_} - -Available operations are ``sum``, ``avg``, ``max``, ``min``, ``stddev``, ``stddev_samp``, -``stddev_pop``, ``variance``, ``var_samp`` and ``var_pop`` - -TableAggOpOrderBy -***************** - -.. parsed-literal:: - {column: OrderByEnum_} - - - OrderByEnum -*********** +########### .. code-block:: graphql @@ -508,6 +486,27 @@ OrderByEnum desc_nulls_last } +AggregateOrderBy +################ + +Count aggregate + +.. parsed-literal:: + {count: OrderByEnum_} + +Operation aggregate + +.. parsed-literal:: + {op_name: TableAggOpOrderBy_} + +Available operations are ``sum``, ``avg``, ``max``, ``min``, ``stddev``, ``stddev_samp``, +``stddev_pop``, ``variance``, ``var_samp`` and ``var_pop`` + +TableAggOpOrderBy +&&&&&&&&&&&&&&&&& + +.. parsed-literal:: + {column: OrderByEnum_} .. _PaginationExp: diff --git a/docs/graphql/manual/api-reference/schema-metadata-api/custom-functions.rst b/docs/graphql/manual/api-reference/schema-metadata-api/custom-functions.rst index 25b12937ed4f4..fdb7de05f8948 100644 --- a/docs/graphql/manual/api-reference/schema-metadata-api/custom-functions.rst +++ b/docs/graphql/manual/api-reference/schema-metadata-api/custom-functions.rst @@ -6,20 +6,23 @@ Schema/Metadata API Reference: Custom Functions :depth: 1 :local: -Add or remove a custom SQL function to Hasura GraphQL Engine's metadata using following API. +Track/untrack a custom SQL function in Hasura GraphQL engine. -.. Note:: - - Only custom functions added to metadata are available for ``querying/subscribing`` data over **GraphQL** API. +Only tracked custom functions are available for querying/mutating/subscribing data over the GraphQL API. .. _track_function: track_function -------------- -``track_function`` is used to add a custom SQL function. +``track_function`` is used to add a custom SQL function to the GraphQL schema. + +Currently, only functions which satisfy the following constraints can be exposed over the GraphQL API +(*terminology from* `Postgres docs `__): -Refer :ref:`this ` for constraints on supported functions. +- **Function behaviour**: ONLY ``STABLE`` or ``IMMUTABLE`` +- **Return type**: MUST be ``SETOF `` +- **Argument modes**: ONLY ``IN`` Add a SQL function ``search_articles``: @@ -42,7 +45,7 @@ Add a SQL function ``search_articles``: untrack_function ---------------- -``untrack_function`` is used to remove a SQL function from metadata. +``untrack_function`` is used to remove a SQL function from the GraphQL schema. Remove a SQL function ``search_articles``: diff --git a/docs/graphql/manual/api-reference/schema-metadata-api/event-triggers.rst b/docs/graphql/manual/api-reference/schema-metadata-api/event-triggers.rst index f09c0626ab6f2..2486c93e8971e 100644 --- a/docs/graphql/manual/api-reference/schema-metadata-api/event-triggers.rst +++ b/docs/graphql/manual/api-reference/schema-metadata-api/event-triggers.rst @@ -136,8 +136,8 @@ Args syntax .. _TriggerName: -``TriggerName`` -&&&&&&&&&&&&&&& +TriggerName +&&&&&&&&&&& .. parsed-literal:: @@ -145,8 +145,8 @@ Args syntax .. _OperationSpec: -``OperationSpec`` -&&&&&&&&&&&&&&&&& +OperationSpec +&&&&&&&&&&&&& .. list-table:: :header-rows: 1 @@ -166,8 +166,8 @@ Args syntax .. _HeaderFromValue: -``HeaderFromValue`` -&&&&&&&&&&&&&&&&&&& +HeaderFromValue +&&&&&&&&&&&&&&& .. list-table:: :header-rows: 1 @@ -187,8 +187,8 @@ Args syntax .. _HeaderFromEnv: -``HeaderFromEnv`` -&&&&&&&&&&&&&&&&& +HeaderFromEnv +&&&&&&&&&&&&& .. list-table:: :header-rows: 1 @@ -208,8 +208,8 @@ Args syntax .. _EventTriggerColumns: -``EventTriggerColumns`` -&&&&&&&&&&&&&&&&&&&&&&& +EventTriggerColumns +&&&&&&&&&&&&&&&&&&& .. parsed-literal:: :class: haskell-pre diff --git a/docs/graphql/manual/api-reference/schema-metadata-api/index.rst b/docs/graphql/manual/api-reference/schema-metadata-api/index.rst index c32c36c6dcab4..699a448b18134 100644 --- a/docs/graphql/manual/api-reference/schema-metadata-api/index.rst +++ b/docs/graphql/manual/api-reference/schema-metadata-api/index.rst @@ -1,12 +1,12 @@ -Schema/Metadata API Reference -============================= +Schema / Metadata API Reference +=============================== .. contents:: Table of contents :backlinks: none :depth: 1 :local: -The Schema/Metadata API provides the following features: +The Schema / Metadata API provides the following features: 1. Execute SQL on the underlying Postgres database, supports schema modifying actions. 2. Modify Hasura metadata (permissions rules and relationships). @@ -30,12 +30,17 @@ Request structure "args": } -Body syntax: :ref:`Query ` +Request body +^^^^^^^^^^^^ -.. _query_syntax: +.. parsed-literal:: -``Query`` -^^^^^^^^^ + Query_ + +.. _Query: + +Query +***** .. list-table:: :header-rows: 1 @@ -60,7 +65,11 @@ The various types of queries are listed in the following table: * - ``type`` - ``args`` - - ``Synopsis`` + - Synopsis + + * - **bulk** + - :ref:`Query ` array + - Execute multiple operations in a single query * - :ref:`run_sql` - :ref:`run_sql_args ` @@ -134,10 +143,6 @@ The various types of queries are listed in the following table: - :ref:`set_permission_comment_args ` - Set comment on an existing permission - * - ``"bulk"`` - - :ref:`Query ` array - - Execute multiple operations in a single query - * - :ref:`create_event_trigger` - :ref:`create_event_trigger_args ` - Create or replace event trigger diff --git a/docs/graphql/manual/api-reference/schema-metadata-api/permission.rst b/docs/graphql/manual/api-reference/schema-metadata-api/permission.rst index 3d5e2d8eaa233..104dbd03c8ea7 100644 --- a/docs/graphql/manual/api-reference/schema-metadata-api/permission.rst +++ b/docs/graphql/manual/api-reference/schema-metadata-api/permission.rst @@ -121,8 +121,8 @@ Args syntax .. _InsertPermission: -``InsertPermission`` -&&&&&&&&&&&&&&&&&&&& +InsertPermission +&&&&&&&&&&&&&&&& .. list-table:: :header-rows: 1 @@ -235,8 +235,8 @@ Args syntax .. _SelectPermission: -``SelectPermission`` -&&&&&&&&&&&&&&&&&&&& +SelectPermission +&&&&&&&&&&&&&&&& .. list-table:: :header-rows: 1 @@ -357,8 +357,8 @@ Args syntax .. _UpdatePermission: -``UpdatePermission`` -&&&&&&&&&&&&&&&&&&&& +UpdatePermission +&&&&&&&&&&&&&&&& .. list-table:: :header-rows: 1 @@ -468,8 +468,8 @@ Args syntax .. _DeletePermission: -``DeletePermission`` -&&&&&&&&&&&&&&&&&&&& +DeletePermission +&&&&&&&&&&&&&&&& .. list-table:: :header-rows: 1 diff --git a/docs/graphql/manual/api-reference/schema-metadata-api/relationship.rst b/docs/graphql/manual/api-reference/schema-metadata-api/relationship.rst index 891b6d30208bc..facce164c1ecf 100644 --- a/docs/graphql/manual/api-reference/schema-metadata-api/relationship.rst +++ b/docs/graphql/manual/api-reference/schema-metadata-api/relationship.rst @@ -6,8 +6,7 @@ Schema/Metadata API Reference: Relationships :depth: 1 :local: -Relationships are used to capture the connectedness of data amongst tables. -In general, when retrieving data from tables, it is very helpful if we can also +When retrieving data from tables, it is very helpful if we can also fetch the related data alongside the columns. This is where relationships come in. They can be considered as pseudo columns for a table to access the related data. @@ -17,7 +16,7 @@ For a simple ``article/author`` schema, the following relationships exist: - ``author`` of an ``article`` - ``articles`` of an ``author`` -As you may have noticed, there are two kinds of relationships: +There are two kinds of relationships: - one-to-one or ``object relationships`` (e.g. ``author``). - one-to-many or ``array relationships`` (e.g. ``articles``). @@ -134,8 +133,8 @@ Args syntax .. _ObjRelUsing: -``ObjRelUsing`` -&&&&&&&&&&&&&&& +ObjRelUsing +&&&&&&&&&&& .. list-table:: :header-rows: 1 @@ -159,8 +158,8 @@ Args syntax and ``manual_mapping``. -``ObjRelUsingManualMapping`` -&&&&&&&&&&&&&&&&&&&&&&&&&&&& +ObjRelUsingManualMapping +&&&&&&&&&&&&&&&&&&&&&&&& .. list-table:: :header-rows: 1 @@ -291,8 +290,8 @@ Args syntax .. _ArrRelUsing: -``ArrRelUsing`` -&&&&&&&&&&&&&&& +ArrRelUsing +&&&&&&&&&&& .. list-table:: :header-rows: 1 @@ -310,8 +309,8 @@ Args syntax - ArrRelUsingManualMapping_ - Manual mapping of table and columns -``ArrRelUsingFKeyOn`` -&&&&&&&&&&&&&&&&&&&&& +ArrRelUsingFKeyOn +&&&&&&&&&&&&&&&&& .. list-table:: :header-rows: 1 @@ -329,8 +328,8 @@ Args syntax - :ref:`PGColumn` - Name of the column with foreign key constraint -``ArrRelUsingManualMapping`` -&&&&&&&&&&&&&&&&&&&&&&&&&&&& +ArrRelUsingManualMapping +&&&&&&&&&&&&&&&&&&&&&&&& .. list-table:: :header-rows: 1 diff --git a/docs/graphql/manual/api-reference/schema-metadata-api/run-sql.rst b/docs/graphql/manual/api-reference/schema-metadata-api/run-sql.rst index 9776d332f668b..06efe2361f50e 100644 --- a/docs/graphql/manual/api-reference/schema-metadata-api/run-sql.rst +++ b/docs/graphql/manual/api-reference/schema-metadata-api/run-sql.rst @@ -11,6 +11,11 @@ Schema/Metadata API Reference: Run SQL run_sql ------- +``run_sql`` can be used to run arbitrary SQL statements. + +Multiple SQL statements can be separated by a ``;``, however, only the result of the last SQL statement will be +returned. + .. admonition:: Admin-only This is an admin-only query, i.e. the query can only be executed by a @@ -29,9 +34,6 @@ Use cases 1. To execute DDL operations that are not supported by the console (e.g. managing indexes). 2. Run custom DML queries from backend microservices instead of installing libraries to speak to Postgres. -``run_sql`` can be used to run arbitrary SQL statements. Multiple SQL statements can be separated by a -"``;``", however, only the result of the last sql statement will be returned. - An example: .. code-block:: http @@ -165,7 +167,7 @@ The response is a JSON Object with the following structure. .. note:: The first row in the ``result`` (when present) will be the names of the columns. -More examples +Some examples ^^^^^^^^^^^^^ A query returning results. diff --git a/docs/graphql/manual/api-reference/schema-metadata-api/table-view.rst b/docs/graphql/manual/api-reference/schema-metadata-api/table-view.rst index c284c23441032..cc8d8279fd8fa 100644 --- a/docs/graphql/manual/api-reference/schema-metadata-api/table-view.rst +++ b/docs/graphql/manual/api-reference/schema-metadata-api/table-view.rst @@ -1,23 +1,21 @@ Schema/Metadata API Reference: Tables/Views -============================================ +=========================================== .. contents:: Table of contents :backlinks: none :depth: 1 :local: -Add or remove a table/view to Hasura GraphQL Engine's metadata using following API. +Track/untrack a table/view in Hasura GraphQL engine -.. Note:: - - Only tables/views added to metadata are available for ``querying/mutating/subscribing`` data over **GraphQL** API. +Only tracked tables/views are available for querying/mutating/subscribing data over the GraphQL API. .. _track_table: track_table ----------- -``track_table`` is used to add a table/view. +``track_table`` is used to add a table/view to the GraphQL schema. Add a table/view ``author``: @@ -57,7 +55,7 @@ Args syntax untrack_table ------------- -``untrack_table`` is used to remove a table/view. +``untrack_table`` is used to remove a table/view from the GraphQL schema. Remove a table/view ``author``: diff --git a/docs/graphql/manual/auth/basics.rst b/docs/graphql/manual/auth/basics.rst index 47b2f3ed0a4e8..18af232a31bfd 100644 --- a/docs/graphql/manual/auth/basics.rst +++ b/docs/graphql/manual/auth/basics.rst @@ -21,19 +21,7 @@ Head to your console and :ref:`create a table ` called ``author`` name TEXT ) -Insert some sample data into the table: - -+-------------+----------+ -| **id** | **name** | -+-------------+----------+ -| 1 | john | -+-------------+----------+ -| 2 | shruti | -+-------------+----------+ -| 3 | celine | -+-------------+----------+ -| 4 | raj | -+-------------+----------+ +Now, insert some sample data into the table using the ``Insert Row`` tab of the ``author`` table. Try out a query --------------- @@ -58,28 +46,26 @@ accepted with **admin** permissions. Add a simple access control rule for a logged in user ----------------------------------------------------- -Let's say that for our app, logged in users are only allowed to fetch their own data. +Let's say that we want to restrict users to fetch only their own data. Head to the ``Permissions`` tab of the ``author`` table. -Let's add a **select** permission for the **user** role on the ``author`` table: +Now add a ``select`` access control rule for the ``user`` role on the ``author`` table: .. image:: ../../../img/graphql/manual/auth/author-select-perms.png -This reads as: +This rule reads as: .. list-table:: :header-rows: 1 - :widths: 15 20 25 40 + :widths: 25 20 45 - * - Table - - Definition + * - Definition - Condition - Representation - * - author - - user's own row - - ``id`` in the row is equal to ``user-id`` from the request session + * - allow user to access only their own row + - ``id`` in the row is equal to ``user-id`` from the request session variable - .. code-block:: json @@ -96,19 +82,31 @@ Now, let's make the same query as above but also include two dynamic authorizati You can notice above how the same query now only includes the right slice of data. -.. admonition:: Permission rules can also use nested object's fields +.. admonition:: Defining access control rules - For example, for an ``article`` table with nested ``author`` table, we can define the select permission as: + Access control, or permission rules can be as complex as you need them to be, even using a nested object's + fields if required. You can use the same operators that you use to filter query results to define + permission rules. See :doc:`filtering query results <../queries/query-filters>` for more details. + + For example, for an ``article`` table with a nested ``author`` table, we can define the select permission as: .. code-block:: json { - "author" : { - "id": { - "_eq": "X-Hasura-User-Id" - } + "_and": + [ + { + "published_on": { "_gt": "31-12-2018" } + }, + { + "author": { + "id": { "_eq": "X-Hasura-User-Id" } + } + } + ] } - } + + This rule reads as: allow selecting an article if it was published after "31-12-2018" and its author is the current user. .. _restrict_columns: diff --git a/docs/graphql/manual/deployment/docker/securing-graphql-endpoint.rst b/docs/graphql/manual/deployment/docker/securing-graphql-endpoint.rst index 67ddeb852a740..0d42808c8677b 100644 --- a/docs/graphql/manual/deployment/docker/securing-graphql-endpoint.rst +++ b/docs/graphql/manual/deployment/docker/securing-graphql-endpoint.rst @@ -25,5 +25,5 @@ Run the docker command with an access-key env var .. note:: - If you're looking at adding authentication and access control to your GraphQL API then head - to :doc:`Authentication / access control <../../auth/index>`. + If you're looking at adding access control rules for your data to your GraphQL API then head + to :doc:`Authentication / access control <../auth/index>`. diff --git a/docs/graphql/manual/deployment/heroku/securing-graphql-endpoint.rst b/docs/graphql/manual/deployment/heroku/securing-graphql-endpoint.rst index 880f94d861feb..dea25f8684448 100644 --- a/docs/graphql/manual/deployment/heroku/securing-graphql-endpoint.rst +++ b/docs/graphql/manual/deployment/heroku/securing-graphql-endpoint.rst @@ -35,5 +35,5 @@ In case you're using the CLI to open the Hasura console, use the ``access-key`` .. note:: - If you're looking at adding authentication and access control to your GraphQL API then head - to :doc:`Authentication / access control <../../auth/index>`. \ No newline at end of file + If you're looking at adding access control rules for your data to your GraphQL API then head + to :doc:`Authentication / access control <../auth/index>`. \ No newline at end of file diff --git a/docs/graphql/manual/deployment/heroku/updating.rst b/docs/graphql/manual/deployment/heroku/updating.rst index 5c163b36650dd..46c7413f1eee9 100644 --- a/docs/graphql/manual/deployment/heroku/updating.rst +++ b/docs/graphql/manual/deployment/heroku/updating.rst @@ -23,19 +23,23 @@ Step 1: Clone the Hasura GraphQL engine Heroku app The Hasura app with Heroku buildpack/configuration is available at: https://github.com/hasura/graphql-engine-heroku +Clone the above repository. + If you already have this, then pull the latest changes which will have the updated GraphQL engine docker image. Step 2: Attach your Heroku app ------------------------------ Let's say your Heroku app is called ``hasura-heroku`` and is running on ``https://hasura-heroku.herokuapp.com``. + Use the `Heroku CLI `_ to configure the git repo you cloned in Step 1 to be able to push to this app. .. code-block:: bash - # Replace hasura-heroku with your Heroku app's name - $ heroku git:remote -a hasura-heroku + # Replace with your Heroku app's name + $ heroku git:remote -a + $ heroku stack:set container -a Step 3: Git push to deploy the latest Hasura GraphQL engine ----------------------------------------------------------- diff --git a/docs/graphql/manual/deployment/kubernetes/securing-graphql-endpoint.rst b/docs/graphql/manual/deployment/kubernetes/securing-graphql-endpoint.rst index 58d075380c75f..087aebd4898f3 100644 --- a/docs/graphql/manual/deployment/kubernetes/securing-graphql-endpoint.rst +++ b/docs/graphql/manual/deployment/kubernetes/securing-graphql-endpoint.rst @@ -48,5 +48,5 @@ In case you're using the CLI to open the Hasura console, use the ``access-key`` .. note:: - If you're looking at adding authentication and access control to your GraphQL API then head - to :doc:`Authentication / access control <../../auth/index>`. \ No newline at end of file + If you're looking at adding access control rules for your data to your GraphQL API then head + to :doc:`Authentication / access control <../auth/index>`. \ No newline at end of file diff --git a/docs/graphql/manual/deployment/postgres-permissions.rst b/docs/graphql/manual/deployment/postgres-permissions.rst index b4af3f8cedf7d..c4d8c72cfb54f 100644 --- a/docs/graphql/manual/deployment/postgres-permissions.rst +++ b/docs/graphql/manual/deployment/postgres-permissions.rst @@ -51,11 +51,16 @@ Here's a sample SQL block that you can run on your database to create the right -- tables/schemas - you want give to hasura. If you want expose the public -- schema for GraphQL query then give permissions on public schema to the -- hasura user. + -- Be careful to use these in your production db. Consult the postgres manual or + -- your DBA and give appropriate permissions. -- grant all privileges on all tables in the public schema. This can be customised: -- For example, if you only want to use GraphQL regular queries and not mutations, - -- then you can: GRANT SELECT ON ALL TABLES... + -- then you can set: GRANT SELECT ON ALL TABLES... GRANT ALL ON ALL TABLES IN SCHEMA public TO hasurauser; GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO hasurauser; - -- Similarly repeat this for other schemas, if you have. + -- Similarly add this for other schemas, if you have any. + -- GRANT USAGE ON SCHEMA TO hasurauser; + -- GRANT ALL ON ALL TABLES IN SCHEMA TO hasurauser; + -- GRANT ALL ON ALL SEQUENCES IN SCHEMA TO hasurauser; diff --git a/docs/graphql/manual/deployment/securing-graphql-endpoint.rst b/docs/graphql/manual/deployment/securing-graphql-endpoint.rst index 1184eea1d2e7e..3901afcd92428 100644 --- a/docs/graphql/manual/deployment/securing-graphql-endpoint.rst +++ b/docs/graphql/manual/deployment/securing-graphql-endpoint.rst @@ -18,6 +18,6 @@ access to your GraphQL endpoint and the Hasura console: .. note:: - If you're looking at adding authentication and access control to your GraphQL API then head + If you're looking at adding access control rules for your data to your GraphQL API then head to :doc:`Authentication / access control <../auth/index>`. diff --git a/docs/graphql/manual/getting-started/first-event-trigger.rst b/docs/graphql/manual/getting-started/first-event-trigger.rst index fc9f6bcd72b92..7c57d11196c77 100644 --- a/docs/graphql/manual/getting-started/first-event-trigger.rst +++ b/docs/graphql/manual/getting-started/first-event-trigger.rst @@ -15,16 +15,14 @@ Create a table Head to the Hasura console, navigate to ``Data -> Create table`` and create a sample table called ``profile`` with the following columns: -+----------+----------+ -| **profile** | -+----------+----------+ -| id | integer | -+----------+----------+ -| name | text | -+----------+----------+ +.. code-block:: sql -.. image:: ../../../img/graphql/manual/getting-started/create-profile-table.png + profile ( + id INT PRIMARY KEY, + name TEXT + ) +.. image:: ../../../img/graphql/manual/getting-started/create-profile-table.png Setup an event trigger ---------------------- @@ -44,7 +42,7 @@ This sets up our webhook ``https://httpbin.org/post`` to receive database change Watch the trigger in action --------------------------- -1. Insert some sample data into the ``profile`` table. +1. Insert some sample data into the ``profile`` table using the ``Insert Row`` tab. 2. Now navigate to the ``Events`` tab and click on the ``echo`` trigger in the left sidebar. 3. Expand the details of an event to see the response from the webhook. diff --git a/docs/graphql/manual/getting-started/first-graphql-query.rst b/docs/graphql/manual/getting-started/first-graphql-query.rst index f1b226375205c..5a9d4c6d22087 100644 --- a/docs/graphql/manual/getting-started/first-graphql-query.rst +++ b/docs/graphql/manual/getting-started/first-graphql-query.rst @@ -16,29 +16,16 @@ Create a table Head to the Hasura console, navigate to ``Data -> Create table`` and create a sample table called ``profile`` with the following columns: -+----------+----------+ -| **profile** | -+----------+----------+ -| id | integer | -+----------+----------+ -| name | text | -+----------+----------+ +.. code-block:: sql + + profile ( + id INT PRIMARY KEY, + name TEXT + ) .. image:: ../../../img/graphql/manual/getting-started/create-profile-table.png -Insert some sample data into the table: - -+-----------+------------+ -| **id** | **name** | -+-----------+------------+ -| 1 | john | -+-----------+------------+ -| 2 | shruti | -+-----------+------------+ -| 3 | celine | -+-----------+------------+ -| 4 | raj | -+-----------+------------+ +Now, insert some sample data into the table using the ``Insert Row`` tab of the ``profile`` table. Try out a query --------------- diff --git a/docs/graphql/manual/mutations/delete.rst b/docs/graphql/manual/mutations/delete.rst index 6ef3fe585ab52..29d5271c4c4f4 100644 --- a/docs/graphql/manual/mutations/delete.rst +++ b/docs/graphql/manual/mutations/delete.rst @@ -9,7 +9,7 @@ Delete mutation Auto-generated delete mutation schema ------------------------------------- -Here’s the schema for the delete mutation field for a table ``article``: +**For example**, the auto-generated schema for the delete mutation field for a table ``article`` looks like this: .. code-block:: graphql @@ -31,6 +31,8 @@ As you can see from the schema: for filtering options. Objects can be deleted based on filters on their own fields or those in their nested objects. - You can return the number of affected rows and the affected objects (with nested objects) in the response. +See the :ref:`delete mutation API reference ` for the full specifications + .. note:: If a table is not in the ``public`` Postgres schema, the delete mutation field will be of the format diff --git a/docs/graphql/manual/mutations/insert.rst b/docs/graphql/manual/mutations/insert.rst index 0c7dcdb9edfcf..8f6458cad8677 100644 --- a/docs/graphql/manual/mutations/insert.rst +++ b/docs/graphql/manual/mutations/insert.rst @@ -9,7 +9,7 @@ Insert mutation Auto-generated insert mutation schema ------------------------------------- -Here’s the schema for the insert mutation field for a table ``article``: +**For example**, the auto-generated schema for the insert mutation field for a table ``article`` looks like this: .. code-block:: graphql @@ -32,6 +32,8 @@ As you can see from the schema: - You can pass an ``on_conflict`` argument to convert the mutation to an :doc:`upsert mutation ` - You can return the number of affected rows and the affected objects (with nested objects) in the response. +See the :ref:`insert mutation API reference ` for the full specifications + .. note:: If a table is not in the ``public`` Postgres schema, the insert mutation field will be of the format @@ -76,7 +78,9 @@ Insert a single object } } -OR +Insert a single object (using variables) +---------------------------------------- +**Example:** Insert a new ``article`` object and return the inserted article object in the response .. graphiql:: :view_only: @@ -266,6 +270,63 @@ in the response } } +Insert an object with a JSONB column +------------------------------------ +**Example:** Insert a new ``author`` object with a JSONB ``address`` column + +.. graphiql:: + :view_only: + :query: + mutation insert_author($address: jsonb) { + insert_author ( + objects: [ + { + id: 1, + name: "Ash", + address: $address + } + ] + ) { + affected_rows + returning { + id + name + address + } + } + } + :response: + { + "data": { + "insert_author": { + "affected_rows": 1, + "returning": [ + { + "id": 1, + "name": "Ash", + "address": { + "city": "Bengaluru", + "phone": "9090909090", + "state": "Karnataka", + "pincode": 560095, + "street_address": "161, 19th Main Road, Koramangala 6th Block" + } + } + ] + } + } + } + :variables: + { + "address": { + "street_address": "161, 19th Main Road, Koramangala 6th Block", + "city": "Bengaluru", + "phone": "9090909090", + "state": "Karnataka", + "pincode": 560095 + } + } + Set a field to its default value during insert ---------------------------------------------- @@ -308,7 +369,7 @@ To set a field to its ``default`` value, just omit it from the input object, irr } } -Set a field to null during insert +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 diff --git a/docs/graphql/manual/mutations/update.rst b/docs/graphql/manual/mutations/update.rst index 64f45bcce774e..6560a355ad2cf 100644 --- a/docs/graphql/manual/mutations/update.rst +++ b/docs/graphql/manual/mutations/update.rst @@ -9,7 +9,7 @@ Update mutation Auto-generated update mutation schema ------------------------------------- -Here’s the schema for the update mutation field for a table ``article``: +**For example**, the auto-generated schema for the update mutation field for a table ``article`` looks like this: .. code-block:: graphql @@ -33,6 +33,8 @@ As you can see from the schema: for filtering options. Objects can be updated based on filters on their own fields or those in their nested objects. - You can return the number of affected rows and the affected objects (with nested objects) in the response. +See the :ref:`update mutation API reference ` for the full specifications + .. note:: - At least any one of ``_set``, ``_inc`` operators or the jsonb operators ``_append``, ``_prepend``, ``_delete_key``, @@ -83,6 +85,100 @@ Update based on an object's fields } } +Update based on an object's fields (using variables) +---------------------------------------------------- +**Example:** Update the ``title``, ``content`` and ``rating`` of the article with a given ``id``: + +.. graphiql:: + :view_only: + :query: + mutation update_article($id: Int, $changes: article_set_input) { + update_article( + where: {id: {_eq: $id}}, + _set: $changes + ) { + affected_rows + returning { + id + title + content + rating + } + } + } + :response: + { + "data": { + "update_article": { + "affected_rows": 1, + "returning": [ + { + "id": 3, + "title": "lorem ipsum", + "content": "dolor sit amet", + "rating": 2 + } + ] + } + } + } + :variables: + { + "id": 3, + "changes": { + "title": "lorem ipsum", + "content": "dolor sit amet", + "rating": 2 + } + } + +OR + +.. graphiql:: + :view_only: + :query: + mutation update_article($id: Int, $title: String, $content: String, $rating: Int) { + update_article( + where: {id: {_eq: $id}}, + _set: { + title: $title, + content: $content, + rating: $rating + } + ) { + affected_rows + returning { + id + title + content + rating + } + } + } + :response: + { + "data": { + "update_article": { + "affected_rows": 1, + "returning": [ + { + "id": 3, + "title": "lorem ipsum", + "content": "dolor sit amet", + "rating": 2 + } + ] + } + } + } + :variables: + { + "id": 3, + "title": "lorem ipsum", + "content": "dolor sit amet", + "rating": 2 + } + Update based on a nested object's fields ---------------------------------------- **Example:** Update the ``rating`` of all articles authored by "Sidney": @@ -175,7 +271,7 @@ You can append any ``jsonb`` column with another json value by using the ``_appe Since the input is a json value, it should be provided through a variable. -**Example:** Append the json ``{"key1": "value1}`` to ``jsonb`` column ``extra_info`` of ``article`` table: +**Example:** Append the json ``{"key1": "value1"}`` to ``jsonb`` column ``extra_info`` of ``article`` table: .. graphiql:: :view_only: @@ -218,7 +314,7 @@ You can prepend any ``jsonb`` column with another json value by using the ``_pre Since the input is a json value, it should be provided through a variable. -**Example:** Prepend the json ``{"key0": "value0}`` to ``jsonb`` column ``extra_info`` of ``article`` table: +**Example:** Prepend the json ``{"key0": "value0"}`` to ``jsonb`` column ``extra_info`` of ``article`` table: .. graphiql:: :view_only: diff --git a/docs/graphql/manual/mutations/upsert.rst b/docs/graphql/manual/mutations/upsert.rst index 3e5fe1badd4f2..c1dbc7963d521 100644 --- a/docs/graphql/manual/mutations/upsert.rst +++ b/docs/graphql/manual/mutations/upsert.rst @@ -108,15 +108,15 @@ Upsert in nested mutations -------------------------- You can specify ``on_conflict`` clause while inserting nested objects - .. graphiql:: :view_only: :query: mutation upsert_author_article { insert_author( objects: [ - { name: "John", + { id: 10, + name: "John", articles: { data: [ { @@ -146,9 +146,15 @@ You can specify ``on_conflict`` clause while inserting nested objects } -.. note:: +.. admonition:: Edge-cases + + Nested upserts will fail when: - Inserting nested objects fails when: + - In case of an array relationship, parent upsert does not affect any rows (i.e. ``update_columns: []`` for parent + and a conflict occurs) + - In case of an object relationship, nested object upsert does not affect any row (i.e. ``update_columns: []`` for + nested object and a conflict occurs) - - Any of upsert in object relationships does not affect any rows (``update_columns: []``) - - Array relationships are queued for insert and parent insert does not affect any rows (``update_columns: []``) + To allow upserting in these cases, set ``update_columns: []``. By doing this, in case of a + conflict, the conflicted column will be updated with the new value (which is the same value it had before and hence + will effectively leave it unchanged) and will allow the upsert to go through. diff --git a/docs/graphql/manual/queries/aggregation-queries.rst b/docs/graphql/manual/queries/aggregation-queries.rst index f6dc12bca8aff..ccfa5bbed3e2c 100644 --- a/docs/graphql/manual/queries/aggregation-queries.rst +++ b/docs/graphql/manual/queries/aggregation-queries.rst @@ -11,7 +11,7 @@ Available aggregation functions are ``count``, ``sum``, ``avg``, ``max`` and ``m .. note:: - The name of the :ref:`aggregate field ` is of the form ``field-name + _aggregate`` + The name of the :ref:`aggregate field ` is of the form `` + _aggregate`` Fetch aggregated data of an object ---------------------------------- diff --git a/docs/graphql/manual/queries/distinct-queries.rst b/docs/graphql/manual/queries/distinct-queries.rst index 084ca86ebe10a..7007bf6eae160 100644 --- a/docs/graphql/manual/queries/distinct-queries.rst +++ b/docs/graphql/manual/queries/distinct-queries.rst @@ -6,8 +6,10 @@ Distinct query results :depth: 1 :local: -You can fetch rows with only distinct values of a column using the ``distinct_on`` argument. The first ``order_by`` -columns must match the ``distinct_on`` column. See :doc:`sort queries ` for more info on ``order_by``. +You can fetch rows with only distinct values of a column using the ``distinct_on`` argument. + +This requires the data to be first sorted by the column i.e. the ``distinct_on`` column should also be the first +``order_by`` column. See :doc:`sort queries ` for more info on using ``order_by``. .. code-block:: graphql @@ -24,6 +26,8 @@ columns must match the ``distinct_on`` column. See :doc:`sort queries ` salary } +You can see the complete specification of the ``distinct_on`` argument in the :ref:`API reference `. + Fetch results with distinct values of a particular field -------------------------------------------------------- @@ -33,10 +37,13 @@ Fetch results with distinct values of a particular field :view_only: :query: query { - employee( + employee ( distinct_on: [department] - order_by: [{department: asc}, {salary: desc}] - ){ + order_by: [ + {department: asc}, + {salary: desc} + ] + ) { id name department diff --git a/docs/graphql/manual/queries/index.rst b/docs/graphql/manual/queries/index.rst index 8b8230941d014..c44e2c75d01ac 100644 --- a/docs/graphql/manual/queries/index.rst +++ b/docs/graphql/manual/queries/index.rst @@ -16,10 +16,10 @@ All tables of the database tracked by the GraphQL engine can be queried over the If you have a tracked table in your database, its query field is added as a nested field under the ``query_root`` root level type. -Auto-generated query schema ---------------------------- +Auto-generated query field schema +--------------------------------- -**For example**, the auto-generated query schema for an ``author`` table may look like this: +**For example**, the auto-generated schema for the query field for a table ``author`` looks like this: .. code-block:: graphql @@ -31,6 +31,7 @@ Auto-generated query schema order_by: [author_order_by!] ): [author] +See the :doc:`Query API reference <../api-reference/query>` for the full specifications .. note:: diff --git a/docs/graphql/manual/queries/multiple-arguments.rst b/docs/graphql/manual/queries/multiple-arguments.rst index d2902c5fa511f..5165e322540d4 100644 --- a/docs/graphql/manual/queries/multiple-arguments.rst +++ b/docs/graphql/manual/queries/multiple-arguments.rst @@ -6,10 +6,13 @@ Using multiple arguments in a query :depth: 1 :local: -Multiple arguments can be used together in the same query. For example, you can use the ``where`` argument to -filter the results and then use the ``order_by`` argument to sort them. +Multiple arguments can be used together in the same query. -For example, fetch a list of authors and only 2 of their published articles that are sorted by their date of publication: +For example, you can use the ``where`` argument to filter the results and then use the ``order_by`` argument to +sort them. + +**For example**, fetch a list of authors and only 2 of their published articles that are sorted by their date +of publication: .. graphiql:: :view_only: diff --git a/docs/graphql/manual/queries/multiple-queries.rst b/docs/graphql/manual/queries/multiple-queries.rst index 154e33b5e9801..7b14820d48a03 100644 --- a/docs/graphql/manual/queries/multiple-queries.rst +++ b/docs/graphql/manual/queries/multiple-queries.rst @@ -6,10 +6,10 @@ Multiple queries in a request :depth: 1 :local: -If multiple queries are part of the same request, they are executed **parallelly**, the individual responses are +If multiple queries are part of the same request, **they are executed parallelly**, the individual responses are collated and returned. You can fetch objects of different unrelated types in the same query. -For example, fetch a list of ``authors`` and a list of ``articles``: +**For example**, fetch a list of ``authors`` and a list of ``articles``: .. graphiql:: :view_only: diff --git a/docs/graphql/manual/queries/nested-object-queries.rst b/docs/graphql/manual/queries/nested-object-queries.rst index f589f9ff6a159..407f88a3a0c4c 100644 --- a/docs/graphql/manual/queries/nested-object-queries.rst +++ b/docs/graphql/manual/queries/nested-object-queries.rst @@ -207,4 +207,4 @@ Fetch an author whose id is ``1`` and a nested list of articles with aggregated .. note:: - The name of the :ref:`aggregate field ` is of the form ``field-name + _aggregate`` + The name of the :ref:`aggregate field ` is of the form `` + _aggregate`` diff --git a/docs/graphql/manual/queries/pagination.rst b/docs/graphql/manual/queries/pagination.rst index e4b5ff078e7d2..744be976d986c 100644 --- a/docs/graphql/manual/queries/pagination.rst +++ b/docs/graphql/manual/queries/pagination.rst @@ -6,11 +6,15 @@ Paginate query results :depth: 1 :local: -The operators :ref:`limit ` and :ref:`offset ` are used for pagination. -``limit`` specifies the number of rows to retain from the result set -and ``offset`` determines which slice to retain from the results. +The operators ``limit`` and ``offset`` are used for pagination. -The following are examples of pagination in different scenarios: +``limit`` specifies the number of rows to retain from the result set and ``offset`` determines which slice to +retain from the results. + +You can see the complete specification of the ``limit`` and ``offset`` arguments in the +:ref:`API reference `. + +The following are examples of different pagination scenarios: Limit results ------------- diff --git a/docs/graphql/manual/queries/query-filters.rst b/docs/graphql/manual/queries/query-filters.rst index 66fa0a944a6b4..e52936509ed17 100644 --- a/docs/graphql/manual/queries/query-filters.rst +++ b/docs/graphql/manual/queries/query-filters.rst @@ -6,7 +6,7 @@ Filter query results / search queries :depth: 1 :local: -You can use the :ref:`where ` argument in your queries to filter results based on some field’s values (even +You can use the ``where`` argument in your queries to filter results based on some field’s values (even nested objects' fields). You can even use multiple filters in the same ``where`` clause using the ``_and`` or the ``_or`` operators. @@ -43,13 +43,16 @@ For example, to fetch a list of authors who have articles with a rating greater } } -Here ``_eq`` and ``_gt`` are examples of :ref:`comparison operators ` that can be used in the ``where`` +Here ``_eq`` and ``_gt`` are examples of comparison operators that can be used in the ``where`` argument to filter on equality. -Let’s take a look at different operators that can be used to filter results and other advanced use cases: +You can see the complete specification of the ``where`` argument in the :ref:`API reference `. + +Let’s take a look at different comparision operators that can be used to filter results and other advanced use cases: + +Equality operators (_eq, _neq) +------------------------------ -Equality operators (_eq and _neq) ---------------------------------- The ``_eq`` (equal to) or the ``_neq`` (not equal to) operators are compatible with any Postgres type other than ``json`` or ``jsonB`` (like ``Integer``, ``Float``, ``Double``, ``Text``, ``Boolean``, ``Date``/``Time``/``Timestamp``, etc.). @@ -186,6 +189,7 @@ Fetch a list of articles that were published on a certain date (``published_on`` Greater than or less than operators (_gt, _lt, _gte, _lte) ---------------------------------------------------------- + The ``_gt`` (greater than), ``_lt`` (less than), ``_gte`` (greater than or equal to), ``_lte`` (less than or equal to) operators are compatible with any Postgres type other than ``json`` or ``jsonB`` (like ``Integer``, ``Float``, ``Double``, ``Text``, ``Boolean``, ``Date``/``Time``/``Timestamp``, etc.). @@ -310,6 +314,7 @@ Fetch a list of articles that were published on or after date "01/01/2018": List based search operators (_in, _nin) --------------------------------------- + The ``_in`` (in a list) and ``_nin`` (not in list) operators are used to comparing field values to a list of values. They are compatible with any Postgres type other than ``json`` or ``jsonB`` (like ``Integer``, ``Float``, ``Double``, ``Text``, ``Boolean``, ``Date``/``Time``/``Timestamp``, etc.). @@ -399,11 +404,13 @@ Fetch a list of those authors whose names are NOT part of a list: } } -Text search / filter or pattern matching operators --------------------------------------------------- -The ``_like``, ``_nlike``, ``_ilike``, ``_nilike``, ``_similar``, ``_nsimilar`` operators behave exactly like -their `SQL counterparts `__ and are used for -pattern matching on string/Text fields. +Text search or pattern matching operators (_like, _similar, etc.) +----------------------------------------------------------------- + +The ``_like``, ``_nlike``, ``_ilike``, ``_nilike``, ``_similar``, ``_nsimilar`` operators are used for +pattern matching on string/text fields. + +These operators behave exactly like their `SQL counterparts `__ Example: _like ^^^^^^^^^^^^^^ @@ -438,10 +445,14 @@ Fetch a list of articles whose titles contain the word “amet”: } ] +.. note:: + + ``_like`` is case-sensitive. Use ``_ilike`` for case-insensitive search. + Example: _similar ^^^^^^^^^^^^^^^^^ -Fetch a list of authors whose names begin with A or C (``similar`` is case-sensitive): +Fetch a list of authors whose names begin with A or C: .. graphiql:: :view_only: @@ -478,14 +489,108 @@ Fetch a list of authors whose names begin with A or C (``similar`` is case-sensi } } -PostGIS topology operators --------------------------- +.. note:: + + ``_similar`` is case-sensitive + +JSONB operators (_contains, _has_key, etc.) +------------------------------------------- + +The ``_contains``, ``_contained_in``, ``_has_key``, ``_has_key_any`` and ``_has_key_all`` operators are used to filter +based on ``JSONB`` columns. + +For more details on what these operators do, refer to `Postgres docs `__. + +Example: _contains +^^^^^^^^^^^^^^^^^^ +Fetch all authors living within a particular pincode (present in ``address`` JSONB column): + +.. graphiql:: + :view_only: + :query: + query get_authors_in_pincode ($jsonFilter: jsonb){ + author( + where: { + address: {_contains: $jsonFilter } + } + ) { + id + name + address + } + } + :response: + { + "data": { + "author": [ + { + "id": 1, + "name": "Ash", + "address": { + "street_address": "161, 19th Main Road, Koramangala 6th Block", + "city": "Bengaluru", + "state": "Karnataka", + "pincode": 560095, + "phone": "9090909090", + } + } + ] + } + } + :variables: + { + "jsonFilter": { + "pincode": 560095 + } + } + +Example: _has_key +^^^^^^^^^^^^^^^^^ +Fetch authors if the ``phone`` key is present in their JSONB ``address`` column: + +.. graphiql:: + :view_only: + :query: + query get_authors_if_phone { + author( + where: { + address: {_has_key: "phone" } + } + ) { + id + name + address + } + } + :response: + { + "data": { + "author": [ + { + "id": 1, + "name": "Ash", + "address": { + "street_address": "161, 19th Main Road, Koramangala 6th Block", + "city": "Bengaluru", + "state": "Karnataka", + "pincode": 560095, + "phone": "9090909090" + } + } + ] + } + } + + +PostGIS topology operators (_st_contains, _st_crosses, etc.) +------------------------------------------------------------ + +The ``_st_contains``, ``_st_crosses``, ``_st_equals``, ``_st_intersects``, ``_st_overlaps``, ``_st_touches``, +``_st_within`` and ``_st_d_within`` operators are used to filter based on ``geometry`` like columns. -The ``_st_contains``, ``_st_crosses``, ``_st_equals``, ``_st_intersects``, ``_st_overlaps``, -``_st_touches``, ``_st_within`` and ``_st_d_within`` operators are used to filter ``geometry`` like columns. For more details on what these operators do, refer to `PostGIS docs `__. -Use ``json`` (`GeoJSON `__) representation of ``geometry`` values in +Use JSON (`GeoJSON `__) representation of ``geometry`` values in ``variables`` as shown in the following examples: @@ -587,8 +692,9 @@ Fetch a list of geometry values which are 3 units from given ``point`` value: } } -Filter or check for null values -------------------------------- +Filter or check for null values (_is_null) +------------------------------------------ + Checking for null values can be achieved using the ``_is_null`` operator. Example: Filter null values in a field @@ -635,8 +741,8 @@ Fetch a list of articles that have a value in the ``published_on`` field: } } -Filter based on failure of some criteria ----------------------------------------- +Filter based on failure of some criteria (_not) +----------------------------------------------- The ``_not`` operator can be used to fetch results for which some condition does not hold true. i.e. to invert the filter set for a condition @@ -696,8 +802,9 @@ Fetch all authors who don't have any published articles: } } -Using multiple filters in the same query ----------------------------------------- +Using multiple filters in the same query (_and, _or) +---------------------------------------------------- + You can group multiple parameters in the same ``where`` argument using the ``_and`` or the ``_or`` operators to filter results based on more than one criteria. @@ -814,6 +921,7 @@ Fetch a list of articles rated more than 4 or published after "01/01/2018": Filter nested objects --------------------- + The ``where`` argument can be used in nested objects as well to filter the nested objects Example: diff --git a/docs/graphql/manual/queries/sorting.rst b/docs/graphql/manual/queries/sorting.rst index c5bf85b558b26..7beedf4b03b75 100644 --- a/docs/graphql/manual/queries/sorting.rst +++ b/docs/graphql/manual/queries/sorting.rst @@ -6,68 +6,23 @@ Sort query results :depth: 1 :local: -Results from your query can be sorted by using the :ref:`order_by ` argument. The argument can be used to sort nested +Results from your query can be sorted by using the ``order_by`` argument. The argument can be used to sort nested objects too. The sort order (ascending vs. descending) is set by specifying ``asc`` or ``desc`` enum value for the column name in the ``order_by`` input object e.g. ``{name: desc}``. -By default, for ascending ordering ``null`` values are returned at the end of the results and for descending ordering ``null`` -values are returned at the start of the results. ``null`` values can be fetched first on ascending ordering by specifying -``asc_nulls_first`` and last on descending ordering by specifying ``desc_nulls_last`` enum value e.g. ``{name: desc_nulls_last}``. +By default, for ascending ordering ``null`` values are returned at the end of the results and for descending +ordering ``null`` values are returned at the start of the results. ``null`` values can be fetched first on +ascending ordering by specifying ``asc_nulls_first`` and last on descending ordering by specifying +``desc_nulls_last`` enum value e.g. ``{name: desc_nulls_last}``. The ``order_by`` argument takes an array of objects to allow sorting by multiple columns. -.. code-block:: graphql +You can also use nested objects' fields to sort the results. Only columns from **object relationships** and +are aggregates from **array relationships** can be used for sorting. - article ( - order_by: [article_order_by!] - ): [article]! - - #order by type for "article" table - input article_order_by { - id: order_by - title: order_by - content: order_by - author_id: order_by - #order by using "author" object relationship columns - author: author_order_by - #order by using "likes" array relationship aggregates - likes_aggregate: likes_aggregate_order_by - } - - #the likes_aggregate_order_by type - input likes_aggregate_order_by { - count: order_by - op_name: likes_op_name_order_by - } - #Available "op_name"s are 'max', 'min', 'sum', 'avg', 'stddev', 'stddev_samp', 'stddev_pop', 'variance', 'var_samp' and 'var_pop' - - #the likes__order_by type - input likes_sum_order_by { - like_id: order_by - } - - #the order_by enum type - enum order_by { - #in the ascending order, nulls last - asc - #in the ascending order, nulls last - asc_nulls_last - #in the ascending order, nulls first - asc_nulls_first - #in the descending order, nulls first - desc - #in the descending order, nulls first - desc_nulls_first - #in the descending order, nulls last - desc_nulls_last - } - - -.. Note:: - Only columns from **object** relationships are allowed for sorting. - Only aggregates from **array** relationships are allowed for sorting. +You can see the complete specification of the ``order_by`` argument in the :ref:`API reference `. The following are example queries for different sorting use cases: @@ -80,7 +35,7 @@ Fetch list of authors sorted by their names in an ascending order: :view_only: :query: query { - author( + author ( order_by: {name: asc} ) { id @@ -127,7 +82,7 @@ Fetch a list of authors sorted by their names with a list of their articles that :view_only: :query: query { - author(order_by: {name: asc}) { + author (order_by: {name: asc}) { id name articles(order_by: {rating: desc}) { @@ -198,16 +153,17 @@ Fetch a list of authors sorted by their names with a list of their articles that } } -Sorting objects based on nested object's fields ------------------------------------------------ -Fetch a list of articles that is sorted by their author's id (descending). -Only columns in object relationships are allowed: +Sorting based on nested object's fields +--------------------------------------- +Only columns in object relationships can be used for sorting. + +Fetch a list of articles that are sorted by their author ids in descending: .. graphiql:: :view_only: :query: query { - article( + article ( order_by: {author: {id: desc}} ) { id @@ -254,15 +210,19 @@ Only columns in object relationships are allowed: } } -Sorting by array relationship aggregates ----------------------------------------- -Fetch a list of authors sorted by their article count. +Sorting based on nested objects' aggregates +------------------------------------------- +Only aggregates in array relationships can be used for sorting. + +Fetch a list of authors sorted by their article count in descending. .. graphiql:: :view_only: :query: query { - author(order_by: {articles_aggregate: {count: desc}}) { + author ( + order_by: {articles_aggregate: {count: desc}} + ) { id name articles_aggregate { @@ -317,8 +277,11 @@ nulls first): :view_only: :query: query { - article( - order_by: [{rating: desc}, {published_on: asc_nulls_first}] + article ( + order_by: [ + {rating: desc}, + {published_on: asc_nulls_first} + ] ) { id rating diff --git a/docs/graphql/manual/schema/basics.rst b/docs/graphql/manual/schema/basics.rst index 13f9dabf8c7e3..c8a585cdb3929 100644 --- a/docs/graphql/manual/schema/basics.rst +++ b/docs/graphql/manual/schema/basics.rst @@ -44,7 +44,7 @@ For example, here is the schema for the ``article`` table in this interface: .. image:: ../../../img/graphql/manual/schema/create-table-graphql.png -The following *object type* and *query/mutation* fields are generated for the ``article`` table we just created: +The following object type and query/mutation fields are generated for the ``article`` table we just created: .. code-block:: graphql @@ -83,12 +83,15 @@ The following *object type* and *query/mutation* fields are generated for the `` where: article_bool_exp! ): article_mutation_response -See the :doc:`API reference <../api-reference/index>` for more details. +See the :doc:`query <../api-reference/query>` and :doc:`mutation <../api-reference/mutation>` +API references for the full specifications + +You can insert some sample data into the tables using the ``Insert Row`` tab of the created tables. Try basic GraphQL queries ------------------------- At this point, you should be able to try out basic GraphQL queries/mutations on the newly created tables using the -console ``GraphiQL`` tab (*you may want to add some test data in the tables first*). +console ``GraphiQL`` tab (*you may want to add some sample data into the tables first*). Here are a couple of examples: diff --git a/docs/graphql/manual/schema/default-values/column-presets.rst b/docs/graphql/manual/schema/default-values/column-presets.rst index af704784e65e7..f354331085d38 100644 --- a/docs/graphql/manual/schema/default-values/column-presets.rst +++ b/docs/graphql/manual/schema/default-values/column-presets.rst @@ -9,16 +9,16 @@ Setting default values for fields using role based column presets Let's say you want certain fields to have their values set automatically when not explicitly passed using session variables or fixed values when a new row is created with a particular :doc:`user role <../../auth/roles-variables>` -Hasura GraphQL engine's column presets let you define role-based default values for any field/column. These values can either -be an Authorization header's value or a static value. +Hasura GraphQL engine's column presets let you define role-based default values for any field/column. These values +can either be a session variable value or a static value. .. admonition:: Column preset restricts mutation access for configured role If a column has a preset defined for a given role, access to the column for mutations will be restricted for users - with said role. + with that role. **Example:** Say we have a field ``user_id`` in a table ``article`` which is to be set to the id of the user, from -the value of the user's Authorization header whenever a new row is added to the ``article`` table. +the value of the user's session variable whenever a new row is added to the ``article`` table. Step 1: Configure a column preset --------------------------------- @@ -34,7 +34,12 @@ setting the preset using a static value or from a session variable. .. image:: ../../../../img/graphql/manual/schema/column-presets-value-options.png For our chosen example, we'll use the ``from session variable`` option and configure the ``user_id`` column to be -automatically populated based on the value of the Authorization header ``X-Hasura-User-Id``. +automatically populated based on the value of ``X-Hasura-User-Id`` session variable. + +.. note:: + + To set a column preset for a nested object's column, simply set the corresponding column preset in the remote + table. Step 2: Run an insert mutation ------------------------------ @@ -43,16 +48,16 @@ Head to the GraphiQL interface in the console and try making an insert mutation following headers (*to run through this example, don't forget to also grant the* ``user`` *role sufficient permissions to select from the* ``article`` *table*): -- ``X-Hasura-role`` --> ``user`` (*to test the behaviour for the configured role*) +- ``X-Hasura-Role`` --> ``user`` (*to test the behaviour for the configured role*) - ``X-Hasura-User-Id`` --> ``1`` (*this is the value we should expect in the* ``user_id`` *field*) -As mentioned earlier, you'll notice when you add the ``X-Hasura-role`` header that the field, ``user_id``, is no longer +As mentioned earlier, you'll notice when you add the ``X-Hasura-Role`` header that the field, ``user_id``, is no longer available as the mutation type's field: .. image:: ../../../../img/graphql/manual/schema/column-preset-schema-change-for-role.png Now, if we run the following insert mutation, we'll see that the ``user_id`` field is indeed being set with the value -passed in the ``X-Hasura-User-Id`` header: +passed in the ``X-Hasura-User-Id`` variable: .. image:: ../../../../img/graphql/manual/schema/column-preset-mutation-result.png diff --git a/install-manifests/README.md b/install-manifests/README.md index 7969953783a12..ee898cbf77986 100644 --- a/install-manifests/README.md +++ b/install-manifests/README.md @@ -4,9 +4,12 @@ Various installation / deployment methods for Hasura GraphQL Engine 1. [Docker Compose](docker-compose) 2. [Docker Compose with HTTPS using Caddy](docker-compose-https) -3. [Docker run](docker-run) -3. [Kubernetes](kubernetes) - +3. [Docker Compose with PostGIS enabled Postgres](docker-compose-postgis) +4. [Docker Compose with pgAdmin](docker-compose-pgadmin) +5. [Docker run](docker-run) +6. [Kubernetes](kubernetes) +7. [Azure Container Instance without Postgres](azure-container) +8. [Azure Container Instances with Postgres](azure-container-with-pg) ## License diff --git a/install-manifests/docker-compose-pgadmin/README.md b/install-manifests/docker-compose-pgadmin/README.md new file mode 100644 index 0000000000000..6ccb8916f029f --- /dev/null +++ b/install-manifests/docker-compose-pgadmin/README.md @@ -0,0 +1,36 @@ +# Hasura GraphQL Engine on Docker with pgAdmin + +This Docker Compose setup runs [Hasura GraphQL Engine](https://github.com/hasura/graphql-engine) along with Postgres and [pgAdmin4](https://www.pgadmin.org/) using `docker-compose`. + +## Pre-requisites + +- [Docker](https://docs.docker.com/install/) +- [Docker Compose](https://docs.docker.com/compose/install/) + +## Usage + +- Clone this repo on a machine where you'd like to deploy graphql engine +- Edit `docker-compose.yaml` and change `PGADMIN_DEFAULT_EMAIL` and `PGADMIN_DEFAULT_PASSWORD` to something secure (default pgAdmin login email/password) default value for above variables are: + - **PGADMIN_DEFAULT_EMAIL:** `pgadmin@example.com` + - **PGADMIN_DEFAULT_PASSWORD:** `admin` +- Read more `Environment Variables` here: https://hub.docker.com/r/dpage/pgadmin4/ +- Edit `docker-compose.yaml` and change `HASURA_GRAPHQL_ACCESS_KEY` to something secure +- `docker-compose up -d` +- Navigate to `http://localhost:5050`, login and add a new server with the following parameters: + General - Name: Hasura + Connection - Host: `hasura` + Username: `postgres` + Password: leave empty + +## Important endpoints + +- GraphQL endpoint will be `http://localhost:8080/v1alpha1/graphql` +- Hasura Console will be available on `http://localhost:8080/console` +- pgAdmin will be available on `http://localhost:5050` + + +## Connecting to External Postgres + +If you want to connect to an external/existing postgres database, replace `HASURA_GRAPHQL_DATABASE_URL` in `docker-compose.yaml` with your database url. + +**Note: localhost will resolve to the container ip inside a docker container, not the host ip** diff --git a/install-manifests/docker-compose-pgadmin/docker-compose.yaml b/install-manifests/docker-compose-pgadmin/docker-compose.yaml new file mode 100644 index 0000000000000..07e0720d759eb --- /dev/null +++ b/install-manifests/docker-compose-pgadmin/docker-compose.yaml @@ -0,0 +1,32 @@ +version: '3.6' +services: + postgres: + image: postgres + restart: always + volumes: + - db_data:/var/lib/postgresql/data + pgadmin: + image: dpage/pgadmin4 + restart: always + depends_on: + - postgres + ports: + - 5050:80 + ## you can change pgAdmin default username/password with below environment variables + environment: + PGADMIN_DEFAULT_EMAIL: pgadmin@example.com + PGADMIN_DEFAULT_PASSWORD: admin + graphql-engine: + image: hasura/graphql-engine:v1.0.0-alpha37 + ports: + - "8080:8080" + depends_on: + - "postgres" + restart: always + environment: + HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:@postgres:5432/postgres + HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console + ## uncomment next line to set an access key + # HASURA_GRAPHQL_ACCESS_KEY: mysecretaccesskey +volumes: + db_data: diff --git a/server/src-lib/Hasura/Events/Lib.hs b/server/src-lib/Hasura/Events/Lib.hs index 6313c317f08ce..f1c4fc4214f41 100644 --- a/server/src-lib/Hasura/Events/Lib.hs +++ b/server/src-lib/Hasura/Events/Lib.hs @@ -65,6 +65,14 @@ data TriggerMeta $(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''TriggerMeta) +data DeliveryInfo + = DeliveryInfo + { diCurrentRetry :: Int + , diMaxRetries :: Int + } deriving (Show, Eq) + +$(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''DeliveryInfo) + data Event = Event { eId :: EventId @@ -75,18 +83,29 @@ data Event , eCreatedAt :: Time.UTCTime } deriving (Show, Eq) -instance ToJSON Event where - toJSON (Event eid (QualifiedObject sn tn) trigger event _ created)= - object [ "id" .= eid - , "table" .= object [ "schema" .= sn - , "name" .= tn - ] - , "trigger" .= trigger - , "event" .= event - , "created_at" .= created +$(deriveFromJSON (aesonDrop 1 snakeCase){omitNothingFields=True} ''Event) + +newtype QualifiedTableStrict = QualifiedTableStrict + { getQualifiedTable :: QualifiedTable + } deriving (Show, Eq) + +instance ToJSON QualifiedTableStrict where + toJSON (QualifiedTableStrict (QualifiedObject sn tn)) = + object [ "schema" .= sn + , "name" .= tn ] -$(deriveFromJSON (aesonDrop 1 snakeCase){omitNothingFields=True} ''Event) +data EventPayload + = EventPayload + { epId :: EventId + , epTable :: QualifiedTableStrict + , epTrigger :: TriggerMeta + , epEvent :: Value + , epDeliveryInfo :: DeliveryInfo + , epCreatedAt :: Time.UTCTime + } deriving (Show, Eq) + +$(deriveToJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''EventPayload) data WebhookRequest = WebhookRequest @@ -266,6 +285,17 @@ tryWebhook logenv pool e = do eventId = eId e headerInfos = etiHeaders eti headers = map encodeHeader headerInfos + eventPayload = EventPayload + { epId = eId e + , epTable = QualifiedTableStrict { getQualifiedTable = eTable e} + , epTrigger = eTrigger e + , epEvent = eEvent e + , epDeliveryInfo = DeliveryInfo + { diCurrentRetry = eTries e + , diMaxRetries = rcNumRetries $ etiRetryConf eti + } + , epCreatedAt = eCreatedAt e + } eeCtx <- asks getter -- wait for counter and then increment beforing making http liftIO $ atomically $ do @@ -276,7 +306,7 @@ tryWebhook logenv pool e = do else modifyTVar' c (+1) let options = addHeaders headers W.defaults decodedHeaders = map (decodeHeader headerInfos) $ options CL.^. W.headers - eitherResp <- runExceptT $ runHTTP options (mkAnyHTTPPost (T.unpack webhook) (Just $ toJSON e)) (Just (ExtraContext createdAt eventId)) + eitherResp <- runExceptT $ runHTTP options (mkAnyHTTPPost (T.unpack webhook) (Just $ toJSON eventPayload)) (Just (ExtraContext createdAt eventId)) --decrement counter once http is done liftIO $ atomically $ do @@ -287,32 +317,32 @@ tryWebhook logenv pool e = do Left err -> case err of HClient excp -> let errMsg = TBS.fromLBS $ encode $ show excp - in runFailureQ pool $ mkInvo e 1000 decodedHeaders errMsg [] + in runFailureQ pool $ mkInvo eventPayload 1000 decodedHeaders errMsg [] HParse _ detail -> let errMsg = TBS.fromLBS $ encode detail - in runFailureQ pool $ mkInvo e 1001 decodedHeaders errMsg [] + in runFailureQ pool $ mkInvo eventPayload 1001 decodedHeaders errMsg [] HStatus errResp -> let respPayload = hrsBody errResp respHeaders = hrsHeaders errResp respStatus = hrsStatus errResp - in runFailureQ pool $ mkInvo e respStatus decodedHeaders respPayload respHeaders + in runFailureQ pool $ mkInvo eventPayload respStatus decodedHeaders respPayload respHeaders HOther detail -> let errMsg = (TBS.fromLBS $ encode detail) - in runFailureQ pool $ mkInvo e 500 decodedHeaders errMsg [] + in runFailureQ pool $ mkInvo eventPayload 500 decodedHeaders errMsg [] Right resp -> let respPayload = hrsBody resp respHeaders = hrsHeaders resp respStatus = hrsStatus resp - in runSuccessQ pool e $ mkInvo e respStatus decodedHeaders respPayload respHeaders + in runSuccessQ pool e $ mkInvo eventPayload respStatus decodedHeaders respPayload respHeaders case finally of Left err -> liftIO $ logger $ L.toEngineLog $ EventInternalErr err Right _ -> return () return eitherResp where - mkInvo :: Event -> Int -> [HeaderConf] -> TBS.TByteString -> [HeaderConf] -> Invocation - mkInvo e' status reqHeaders respBody respHeaders + mkInvo :: EventPayload -> Int -> [HeaderConf] -> TBS.TByteString -> [HeaderConf] -> Invocation + mkInvo ep status reqHeaders respBody respHeaders = let resp = if isInitError status then mkErr respBody else mkResp status respBody respHeaders in Invocation - (eId e') + (epId ep) status - (mkWebhookReq (toJSON e) reqHeaders) + (mkWebhookReq (toJSON ep) reqHeaders) resp addHeaders :: [(N.HeaderName, BS.ByteString)] -> W.Options -> W.Options addHeaders headers opts = foldl (\acc h -> acc CL.& W.header (fst h) CL..~ [snd h] ) opts headers