"use strict";(self.webpackChunkdocs_website=self.webpackChunkdocs_website||[]).push([[27585],{15680:(e,n,t)=>{t.d(n,{xA:()=>u,yg:()=>g});var a=t(96540);function i(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function r(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function s(e){for(var n=1;n=0||(i[t]=e[t]);return i}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}var l=a.createContext({}),p=function(e){var n=a.useContext(l),t=n;return e&&(t="function"==typeof e?e(n):s(s({},n),e)),t},u=function(e){var n=p(e.components);return a.createElement(l.Provider,{value:n},e.children)},d="mdxType",c={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},m=a.forwardRef((function(e,n){var t=e.components,i=e.mdxType,r=e.originalType,l=e.parentName,u=o(e,["components","mdxType","originalType","parentName"]),d=p(t),m=i,g=d["".concat(l,".").concat(m)]||d[m]||c[m]||r;return t?a.createElement(g,s(s({ref:n},u),{},{components:t})):a.createElement(g,s({ref:n},u))}));function g(e,n){var t=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var r=t.length,s=new Array(r);s[0]=m;var o={};for(var l in n)hasOwnProperty.call(n,l)&&(o[l]=n[l]);o.originalType=e,o[d]="string"==typeof e?e:i,s[1]=o;for(var p=2;p{t.r(n),t.d(n,{assets:()=>u,contentTitle:()=>l,default:()=>g,frontMatter:()=>o,metadata:()=>p,toc:()=>d});t(96540);var a=t(15680);function i(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function r(e,n){return n=null!=n?n:{},Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):function(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))})),e}function s(e,n){if(null==e)return{};var t,a,i=function(e,n){if(null==e)return{};var t,a,i={},r=Object.keys(e);for(a=0;a=0||(i[t]=e[t]);return i}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}const o={title:"DataHub OpenAPI Guide",sidebar_label:"OpenAPI Guide",slug:"/api/openapi/openapi-usage-guide",custom_edit_url:"https://github.com/datahub-project/datahub/blob/master/docs/api/openapi/openapi-usage-guide.md"},l="DataHub OpenAPI Guide",p={unversionedId:"docs/api/openapi/openapi-usage-guide",id:"docs/api/openapi/openapi-usage-guide",title:"DataHub OpenAPI Guide",description:"Why OpenAPI",source:"@site/genDocs/docs/api/openapi/openapi-usage-guide.md",sourceDirName:"docs/api/openapi",slug:"/api/openapi/openapi-usage-guide",permalink:"/docs/api/openapi/openapi-usage-guide",draft:!1,editUrl:"https://github.com/datahub-project/datahub/blob/master/docs/api/openapi/openapi-usage-guide.md",tags:[],version:"current",frontMatter:{title:"DataHub OpenAPI Guide",sidebar_label:"OpenAPI Guide",slug:"/api/openapi/openapi-usage-guide",custom_edit_url:"https://github.com/datahub-project/datahub/blob/master/docs/api/openapi/openapi-usage-guide.md"},sidebar:"overviewSidebar",previous:{title:"Access Token Management",permalink:"/docs/api/graphql/token-management"},next:{title:"Timeline API",permalink:"/docs/dev-guides/timeline"}},u={},d=[{value:"Why OpenAPI",id:"why-openapi",level:2},{value:"Locating the OpenAPI endpoints",id:"locating-the-openapi-endpoints",level:2},{value:"Understanding the OpenAPI endpoints",id:"understanding-the-openapi-endpoints",level:2},{value:"Entities (/entities)",id:"entities-entities",level:3},{value:"Relationships (/relationships)",id:"relationships-relationships",level:3},{value:"Timeline (/timeline)",id:"timeline-timeline",level:3},{value:"Platform (/platform)",id:"platform-platform",level:3},{value:"Example Requests",id:"example-requests",level:3},{value:"Entities (/entities) endpoint",id:"entities-entities-endpoint",level:4},{value:"POST (UPSERT)",id:"post-upsert",level:5},{value:"POST (CREATE)",id:"post-create",level:5},{value:"GET",id:"get",level:5},{value:"DELETE",id:"delete",level:5},{value:"Postman Collection",id:"postman-collection",level:4},{value:"Relationships (/relationships) endpoint",id:"relationships-relationships-endpoint",level:4},{value:"GET",id:"get-1",level:5},{value:"Programmatic Usage",id:"programmatic-usage",level:2},{value:"Writing metadata events to the /platform endpoints",id:"writing-metadata-events-to-the-platform-endpoints",level:3},{value:"OpenAPI v3 Features",id:"openapi-v3-features",level:2},{value:"Conditional Writes",id:"conditional-writes",level:3},{value:"Batch Get",id:"batch-get",level:3},{value:"Generic Patching",id:"generic-patching",level:3},{value:"Advanced JSON Patch for Arrays",id:"advanced-json-patch-for-arrays",level:4},{value:"Primary Key Definition and Path Construction",id:"primary-key-definition-and-path-construction",level:4},{value:"Patch Operation Examples",id:"patch-operation-examples",level:4}],c={toc:d},m="wrapper";function g(e){var{components:n}=e,t=s(e,["components"]);return(0,a.yg)(m,r(function(e){for(var n=1;n\' \\\n--data-raw \'[\n {\n "aspect": {\n "__type": "SchemaMetadata",\n "schemaName": "SampleHdfsSchema",\n "platform": "urn:li:dataPlatform:platform",\n "platformSchema": {\n "__type": "MySqlDDL",\n "tableSchema": "schema"\n },\n "version": 0,\n "created": {\n "time": 1621882982738,\n "actor": "urn:li:corpuser:etl",\n "impersonator": "urn:li:corpuser:jdoe"\n },\n "lastModified": {\n "time": 1621882982738,\n "actor": "urn:li:corpuser:etl",\n "impersonator": "urn:li:corpuser:jdoe"\n },\n "hash": "",\n "fields": [\n {\n "fieldPath": "county_fips_codefg",\n "jsonPath": "null",\n "nullable": true,\n "description": "null",\n "type": {\n "type": {\n "__type": "StringType"\n }\n },\n "nativeDataType": "String()",\n "recursive": false\n },\n {\n "fieldPath": "county_name",\n "jsonPath": "null",\n "nullable": true,\n "description": "null",\n "type": {\n "type": {\n "__type": "StringType"\n }\n },\n "nativeDataType": "String()",\n "recursive": false\n }\n ]\n },\n "entityType": "dataset",\n "entityUrn": "urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)"\n }\n]\'\n')),(0,a.yg)("h5",{id:"post-create"},"POST (CREATE)"),(0,a.yg)("p",null,"The second POST example will write the update ONLY if the entity doesn't exist. If the entity does exist the\ncommand will return an error instead of overwriting the entity."),(0,a.yg)("p",null,"In this example we've added a URL parameter ",(0,a.yg)("inlineCode",{parentName:"p"},"createEntityIfNotExists=true")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-shell"},"curl --location --request POST 'localhost:8080/openapi/entities/v1/?createEntityIfNotExists=true' \\\n--header 'Content-Type: application/json' \\\n--header 'Accept: application/json' \\\n--header 'Authorization: Bearer ' \\\n--data-raw ''\n")),(0,a.yg)("p",null,"If the entity doesn't exist the response will be identical to the previous example. In the case where the entity already exists,\nthe following error will occur."),(0,a.yg)("blockquote",null,(0,a.yg)("p",{parentName:"blockquote"},"422 ValidationExceptionCollection{EntityAspect:(urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD),schemaMetadata) Exceptions: ","[com.linkedin.metadata.aspect.plugins.validation.AspectValidationException: Cannot perform CREATE if not exists since the entity key already exists.]","}")),(0,a.yg)("h5",{id:"get"},"GET"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-shell"},"curl --location --request GET 'localhost:8080/openapi/entities/v1/latest?urns=urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)&aspectNames=schemaMetadata' \\\n--header 'Accept: application/json' \\\n--header 'Authorization: Bearer '\n")),(0,a.yg)("h5",{id:"delete"},"DELETE"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-shell"},"curl --location --request DELETE 'localhost:8080/openapi/entities/v1/?urns=urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)&soft=true' \\\n--header 'Accept: application/json' \\\n--header 'Authorization: Bearer '\n")),(0,a.yg)("h4",{id:"postman-collection"},"Postman Collection"),(0,a.yg)("p",null,"Collection includes a POST, GET, and DELETE for a single entity with a SchemaMetadata aspect"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "info": {\n "_postman_id": "87b7401c-a5dc-47e4-90b4-90fe876d6c28",\n "name": "DataHub OpenAPI",\n "description": "A description",\n "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"\n },\n "item": [\n {\n "name": "entities/v1",\n "item": [\n {\n "name": "post Entities 1",\n "request": {\n "method": "POST",\n "header": [\n {\n "key": "Content-Type",\n "value": "application/json"\n },\n {\n "key": "Accept",\n "value": "application/json"\n }\n ],\n "body": {\n "mode": "raw",\n "raw": "[\\n {\\n \\"aspect\\": {\\n \\"__type\\": \\"SchemaMetadata\\",\\n \\"schemaName\\": \\"SampleHdfsSchema\\",\\n \\"platform\\": \\"urn:li:dataPlatform:platform\\",\\n \\"platformSchema\\": {\\n \\"__type\\": \\"MySqlDDL\\",\\n \\"tableSchema\\": \\"schema\\"\\n },\\n \\"version\\": 0,\\n \\"created\\": {\\n \\"time\\": 1621882982738,\\n \\"actor\\": \\"urn:li:corpuser:etl\\",\\n \\"impersonator\\": \\"urn:li:corpuser:jdoe\\"\\n },\\n \\"lastModified\\": {\\n \\"time\\": 1621882982738,\\n \\"actor\\": \\"urn:li:corpuser:etl\\",\\n \\"impersonator\\": \\"urn:li:corpuser:jdoe\\"\\n },\\n \\"hash\\": \\"\\",\\n \\"fields\\": [\\n {\\n \\"fieldPath\\": \\"county_fips_codefg\\",\\n \\"jsonPath\\": \\"null\\",\\n \\"nullable\\": true,\\n \\"description\\": \\"null\\",\\n \\"type\\": {\\n \\"type\\": {\\n \\"__type\\": \\"StringType\\"\\n }\\n },\\n \\"nativeDataType\\": \\"String()\\",\\n \\"recursive\\": false\\n },\\n {\\n \\"fieldPath\\": \\"county_name\\",\\n \\"jsonPath\\": \\"null\\",\\n \\"nullable\\": true,\\n \\"description\\": \\"null\\",\\n \\"type\\": {\\n \\"type\\": {\\n \\"__type\\": \\"StringType\\"\\n }\\n },\\n \\"nativeDataType\\": \\"String()\\",\\n \\"recursive\\": false\\n }\\n ]\\n },\\n \\"aspectName\\": \\"schemaMetadata\\",\\n \\"entityType\\": \\"dataset\\",\\n \\"entityUrn\\": \\"urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)\\"\\n }\\n]",\n "options": {\n "raw": {\n "language": "json"\n }\n }\n },\n "url": {\n "raw": "{{baseUrl}}/openapi/entities/v1/",\n "host": ["{{baseUrl}}"],\n "path": ["openapi", "entities", "v1", ""]\n }\n },\n "response": [\n {\n "name": "OK",\n "originalRequest": {\n "method": "POST",\n "header": [],\n "body": {\n "mode": "raw",\n "raw": "[\\n {\\n \\"aspect\\": {\\n \\"value\\": \\"\\"\\n },\\n \\"aspectName\\": \\"aliquip ipsum tempor\\",\\n \\"entityType\\": \\"ut est\\",\\n \\"entityUrn\\": \\"enim in nulla\\",\\n \\"entityKeyAspect\\": {\\n \\"value\\": \\"\\"\\n }\\n },\\n {\\n \\"aspect\\": {\\n \\"value\\": \\"\\"\\n },\\n \\"aspectName\\": \\"ipsum id\\",\\n \\"entityType\\": \\"deser\\",\\n \\"entityUrn\\": \\"aliqua sit\\",\\n \\"entityKeyAspect\\": {\\n \\"value\\": \\"\\"\\n }\\n }\\n]",\n "options": {\n "raw": {\n "language": "json"\n }\n }\n },\n "url": {\n "raw": "{{baseUrl}}/entities/v1/",\n "host": ["{{baseUrl}}"],\n "path": ["entities", "v1", ""]\n }\n },\n "status": "OK",\n "code": 200,\n "_postman_previewlanguage": "json",\n "header": [\n {\n "key": "Content-Type",\n "value": "application/json"\n }\n ],\n "cookie": [],\n "body": "[\\n \\"c\\",\\n \\"labore dolor exercitation in\\"\\n]"\n }\n ]\n },\n {\n "name": "delete Entities",\n "request": {\n "method": "DELETE",\n "header": [\n {\n "key": "Accept",\n "value": "application/json"\n }\n ],\n "url": {\n "raw": "{{baseUrl}}/openapi/entities/v1/?urns=urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)&soft=true",\n "host": ["{{baseUrl}}"],\n "path": ["openapi", "entities", "v1", ""],\n "query": [\n {\n "key": "urns",\n "value": "urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)",\n "description": "(Required) A list of raw urn strings, only supports a single entity type per request."\n },\n {\n "key": "urns",\n "value": "labore dolor exercitation in",\n "description": "(Required) A list of raw urn strings, only supports a single entity type per request.",\n "disabled": true\n },\n {\n "key": "soft",\n "value": "true",\n "description": "Determines whether the delete will be soft or hard, defaults to true for soft delete"\n }\n ]\n }\n },\n "response": [\n {\n "name": "OK",\n "originalRequest": {\n "method": "DELETE",\n "header": [],\n "url": {\n "raw": "{{baseUrl}}/entities/v1/?urns=urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)&soft=true",\n "host": ["{{baseUrl}}"],\n "path": ["entities", "v1", ""],\n "query": [\n {\n "key": "urns",\n "value": "urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)"\n },\n {\n "key": "urns",\n "value": "officia occaecat elit dolor",\n "disabled": true\n },\n {\n "key": "soft",\n "value": "true"\n }\n ]\n }\n },\n "status": "OK",\n "code": 200,\n "_postman_previewlanguage": "json",\n "header": [\n {\n "key": "Content-Type",\n "value": "application/json"\n }\n ],\n "cookie": [],\n "body": "[\\n {\\n \\"rowsRolledBack\\": [\\n {\\n \\"urn\\": \\"urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)\\"\\n }\\n ],\\n \\"rowsDeletedFromEntityDeletion\\": 1\\n }\\n]"\n }\n ]\n },\n {\n "name": "get Entities",\n "protocolProfileBehavior": {\n "disableUrlEncoding": false\n },\n "request": {\n "method": "GET",\n "header": [\n {\n "key": "Accept",\n "value": "application/json"\n }\n ],\n "url": {\n "raw": "{{baseUrl}}/openapi/entities/v1/latest?urns=urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)&aspectNames=schemaMetadata",\n "host": ["{{baseUrl}}"],\n "path": ["openapi", "entities", "v1", "latest"],\n "query": [\n {\n "key": "urns",\n "value": "urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)",\n "description": "(Required) A list of raw urn strings, only supports a single entity type per request."\n },\n {\n "key": "urns",\n "value": "labore dolor exercitation in",\n "description": "(Required) A list of raw urn strings, only supports a single entity type per request.",\n "disabled": true\n },\n {\n "key": "aspectNames",\n "value": "schemaMetadata",\n "description": "The list of aspect names to retrieve"\n },\n {\n "key": "aspectNames",\n "value": "labore dolor exercitation in",\n "description": "The list of aspect names to retrieve",\n "disabled": true\n }\n ]\n }\n },\n "response": [\n {\n "name": "OK",\n "originalRequest": {\n "method": "GET",\n "header": [],\n "url": {\n "raw": "{{baseUrl}}/entities/v1/latest?urns=urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)&aspectNames=schemaMetadata",\n "host": ["{{baseUrl}}"],\n "path": ["entities", "v1", "latest"],\n "query": [\n {\n "key": "urns",\n "value": "non exercitation occaecat",\n "disabled": true\n },\n {\n "key": "urns",\n "value": "urn:li:dataset:(urn:li:dataPlatform:platform,testSchemaIngest,PROD)"\n },\n {\n "key": "aspectNames",\n "value": "non exercitation occaecat",\n "disabled": true\n },\n {\n "key": "aspectNames",\n "value": "schemaMetadata"\n }\n ]\n }\n },\n "status": "OK",\n "code": 200,\n "_postman_previewlanguage": "json",\n "header": [\n {\n "key": "Content-Type",\n "value": "application/json"\n }\n ],\n "cookie": [],\n "body": "{\\n \\"responses\\": {\\n \\"urn:li:dataset:(urn:li:dataPlatform:hive,SampleHiveDataset,PROD)\\": {\\n \\"entityName\\": \\"dataset\\",\\n \\"urn\\": \\"urn:li:dataset:(urn:li:dataPlatform:hive,SampleHiveDataset,PROD)\\",\\n \\"aspects\\": {\\n \\"datasetKey\\": {\\n \\"name\\": \\"datasetKey\\",\\n \\"type\\": \\"VERSIONED\\",\\n \\"version\\": 0,\\n \\"value\\": {\\n \\"__type\\": \\"DatasetKey\\",\\n \\"platform\\": \\"urn:li:dataPlatform:hive\\",\\n \\"name\\": \\"SampleHiveDataset\\",\\n \\"origin\\": \\"PROD\\"\\n },\\n \\"created\\": {\\n \\"time\\": 1650657843351,\\n \\"actor\\": \\"urn:li:corpuser:__datahub_system\\"\\n }\\n },\\n \\"schemaMetadata\\": {\\n \\"name\\": \\"schemaMetadata\\",\\n \\"type\\": \\"VERSIONED\\",\\n \\"version\\": 0,\\n \\"value\\": {\\n \\"__type\\": \\"SchemaMetadata\\",\\n \\"schemaName\\": \\"SampleHiveSchema\\",\\n \\"platform\\": \\"urn:li:dataPlatform:hive\\",\\n \\"version\\": 0,\\n \\"created\\": {\\n \\"time\\": 1581407189000,\\n \\"actor\\": \\"urn:li:corpuser:jdoe\\"\\n },\\n \\"lastModified\\": {\\n \\"time\\": 1581407189000,\\n \\"actor\\": \\"urn:li:corpuser:jdoe\\"\\n },\\n \\"hash\\": \\"\\",\\n \\"platformSchema\\": {\\n \\"__type\\": \\"KafkaSchema\\",\\n \\"documentSchema\\": \\"{\\\\\\"type\\\\\\":\\\\\\"record\\\\\\",\\\\\\"name\\\\\\":\\\\\\"SampleHiveSchema\\\\\\",\\\\\\"namespace\\\\\\":\\\\\\"com.linkedin.dataset\\\\\\",\\\\\\"doc\\\\\\":\\\\\\"Sample Hive dataset\\\\\\",\\\\\\"fields\\\\\\":[{\\\\\\"name\\\\\\":\\\\\\"field_foo\\\\\\",\\\\\\"type\\\\\\":[\\\\\\"string\\\\\\"]},{\\\\\\"name\\\\\\":\\\\\\"field_bar\\\\\\",\\\\\\"type\\\\\\":[\\\\\\"boolean\\\\\\"]}]}\\"\\n },\\n \\"fields\\": [\\n {\\n \\"fieldPath\\": \\"field_foo\\",\\n \\"nullable\\": false,\\n \\"description\\": \\"Foo field description\\",\\n \\"type\\": {\\n \\"type\\": {\\n \\"__type\\": \\"BooleanType\\"\\n }\\n },\\n \\"nativeDataType\\": \\"varchar(100)\\",\\n \\"recursive\\": false,\\n \\"isPartOfKey\\": true\\n },\\n {\\n \\"fieldPath\\": \\"field_bar\\",\\n \\"nullable\\": false,\\n \\"description\\": \\"Bar field description\\",\\n \\"type\\": {\\n \\"type\\": {\\n \\"__type\\": \\"BooleanType\\"\\n }\\n },\\n \\"nativeDataType\\": \\"boolean\\",\\n \\"recursive\\": false,\\n \\"isPartOfKey\\": false\\n }\\n ]\\n },\\n \\"created\\": {\\n \\"time\\": 1650610810000,\\n \\"actor\\": \\"urn:li:corpuser:UNKNOWN\\"\\n }\\n }\\n }\\n }\\n }\\n}"\n }\n ]\n }\n ],\n "auth": {\n "type": "bearer",\n "bearer": [\n {\n "key": "token",\n "value": "{{token}}",\n "type": "string"\n }\n ]\n },\n "event": [\n {\n "listen": "prerequest",\n "script": {\n "type": "text/javascript",\n "exec": [""]\n }\n },\n {\n "listen": "test",\n "script": {\n "type": "text/javascript",\n "exec": [""]\n }\n }\n ]\n }\n ],\n "event": [\n {\n "listen": "prerequest",\n "script": {\n "type": "text/javascript",\n "exec": [""]\n }\n },\n {\n "listen": "test",\n "script": {\n "type": "text/javascript",\n "exec": [""]\n }\n }\n ],\n "variable": [\n {\n "key": "baseUrl",\n "value": "localhost:8080",\n "type": "string"\n },\n {\n "key": "token",\n "value": "eyJhbGciOiJIUzI1NiJ9.eyJhY3RvclR5cGUiOiJVU0VSIiwiYWN0b3JJZCI6ImRhdGFodWIiLCJ0eXBlIjoiUEVSU09OQUwiLCJ2ZXJzaW9uIjoiMSIsImV4cCI6MTY1MDY2MDY1NSwianRpIjoiM2E4ZDY3ZTItOTM5Yi00NTY3LWE0MjYtZDdlMDA1ZGU3NjJjIiwic3ViIjoiZGF0YWh1YiIsImlzcyI6ImRhdGFodWItbWV0YWRhdGEtc2VydmljZSJ9.pp_vW2u1tiiTT7U0nDF2EQdcayOMB8jatiOA8Je4JJA",\n "type": "default"\n }\n ]\n}\n')),(0,a.yg)("h4",{id:"relationships-relationships-endpoint"},"Relationships (/relationships) endpoint"),(0,a.yg)("h5",{id:"get-1"},"GET"),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Sample Request")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-shell"},"curl -X 'GET' \\\n 'http://localhost:8080/openapi/relationships/v1/?urn=urn%3Ali%3Acorpuser%3Adatahub&relationshipTypes=IsPartOf&direction=INCOMING&start=0&count=200' \\\n -H 'accept: application/json'\n")),(0,a.yg)("p",null,(0,a.yg)("strong",{parentName:"p"},"Sample Response")),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "start": 0,\n "count": 2,\n "total": 2,\n "entities": [\n {\n "relationshipType": "IsPartOf",\n "urn": "urn:li:corpGroup:bfoo"\n },\n {\n "relationshipType": "IsPartOf",\n "urn": "urn:li:corpGroup:jdoe"\n }\n ]\n}\n')),(0,a.yg)("h2",{id:"programmatic-usage"},"Programmatic Usage"),(0,a.yg)("p",null,"Programmatic usage of the models can be done through the Java Rest Emitter which includes the generated models. A minimal\nJava project for emitting to the OpenAPI endpoints would need the following dependencies (gradle format):"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-groovy"},"dependencies {\n implementation 'io.acryl:datahub-client:'\n implementation 'org.apache.httpcomponents:httpclient:'\n implementation 'org.apache.httpcomponents:httpasyncclient:'\n}\n")),(0,a.yg)("h3",{id:"writing-metadata-events-to-the-platform-endpoints"},"Writing metadata events to the /platform endpoints"),(0,a.yg)("p",null,"The following code emits metadata events through OpenAPI by constructing a list of ",(0,a.yg)("inlineCode",{parentName:"p"},"UpsertAspectRequest"),"s. Behind the scenes, this is using the ",(0,a.yg)("strong",{parentName:"p"},"/platform/entities/v1")," endpoint to send metadata to GMS."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-java"},'import io.datahubproject.openapi.generated.DatasetProperties;\nimport datahub.client.rest.RestEmitter;\nimport datahub.event.UpsertAspectRequest;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ExecutionException;\n\n\npublic class Main {\n public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {\n RestEmitter emitter = RestEmitter.createWithDefaults();\n\n List requests = new ArrayList<>();\n UpsertAspectRequest upsertAspectRequest = UpsertAspectRequest.builder()\n .entityType("dataset")\n .entityUrn("urn:li:dataset:(urn:li:dataPlatform:bigquery,my-project.my-other-dataset.user-table,PROD)")\n .aspect(new DatasetProperties().description("This is the canonical User profile dataset"))\n .build();\n UpsertAspectRequest upsertAspectRequest2 = UpsertAspectRequest.builder()\n .entityType("dataset")\n .entityUrn("urn:li:dataset:(urn:li:dataPlatform:bigquery,my-project.another-dataset.user-table,PROD)")\n .aspect(new DatasetProperties().description("This is the canonical User profile dataset 2"))\n .build();\n requests.add(upsertAspectRequest);\n requests.add(upsertAspectRequest2);\n System.out.println(emitter.emit(requests, null).get());\n System.exit(0);\n }\n}\n')),(0,a.yg)("h2",{id:"openapi-v3-features"},"OpenAPI v3 Features"),(0,a.yg)("h3",{id:"conditional-writes"},"Conditional Writes"),(0,a.yg)("p",null,"All the create/POST endpoints for aspects support ",(0,a.yg)("inlineCode",{parentName:"p"},"headers")," in the POST body to support batch APIs. See the docs in the\n",(0,a.yg)("a",{parentName:"p",href:"/docs/advanced/mcp-mcl"},"MetadataChangeProposal")," section for the use of these headers to support conditional writes semantics."),(0,a.yg)("h3",{id:"batch-get"},"Batch Get"),(0,a.yg)("p",null,"Batch get endpoints in the form of ",(0,a.yg)("inlineCode",{parentName:"p"},"/v3/entity/{entityName}/batchGet")," exist for all entities. This endpoint allows\nfetching entity and aspects in bulk. In combination with the ",(0,a.yg)("inlineCode",{parentName:"p"},"If-Version-Match")," header it can also retrieve\na specific version of the aspects, however it defaults to the latest aspect version. Currently, this interface is limited\nto returning a single version for each entity/aspect however different versions can be specified across entities."),(0,a.yg)("p",null,"A few example queries are as follows:"),(0,a.yg)("p",null,"Example Request:"),(0,a.yg)("p",null,"Fetch the latest aspects for the given URNs with the url parameter ",(0,a.yg)("inlineCode",{parentName:"p"},"systemMetadata=true")," in order to view the current\nversions of the aspects."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'[\n {\n "urn": "urn:li:dataset:(urn:li:dataPlatform:hive,fct_users_deleted,PROD)",\n "globalTags": {},\n "datasetProperties": {}\n },\n {\n "urn": "urn:li:dataset:(urn:li:dataPlatform:hive,fct_users_created,PROD)",\n "globalTags": {},\n "datasetProperties": {}\n }\n]\n')),(0,a.yg)("p",null,"Example Response:"),(0,a.yg)("p",null,"Notice that ",(0,a.yg)("inlineCode",{parentName:"p"},"systemMetadata")," contains ",(0,a.yg)("inlineCode",{parentName:"p"},'"version": "1"')," for each of the aspects that exist in the system."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'[\n {\n "urn": "urn:li:dataset:(urn:li:dataPlatform:hive,fct_users_deleted,PROD)",\n "datasetProperties": {\n "value": {\n "description": "table containing all the users deleted on a single day",\n "customProperties": {\n "encoding": "utf-8"\n },\n "tags": []\n },\n "systemMetadata": {\n "properties": {\n "clientVersion": "1!0.0.0.dev0",\n "clientId": "acryl-datahub"\n },\n "version": "1",\n "lastObserved": 1720781548776,\n "lastRunId": "file-2024_07_12-05_52_28",\n "runId": "file-2024_07_12-05_52_28"\n }\n }\n },\n {\n "urn": "urn:li:dataset:(urn:li:dataPlatform:hive,fct_users_created,PROD)",\n "datasetProperties": {\n "value": {\n "description": "table containing all the users created on a single day",\n "customProperties": {\n "encoding": "utf-8"\n },\n "tags": []\n },\n "systemMetadata": {\n "properties": {\n "clientVersion": "1!0.0.0.dev0",\n "clientId": "acryl-datahub"\n },\n "version": "1",\n "lastObserved": 1720781548773,\n "lastRunId": "file-2024_07_12-05_52_28",\n "runId": "file-2024_07_12-05_52_28"\n }\n },\n "globalTags": {\n "value": {\n "tags": [\n {\n "tag": "urn:li:tag:NeedsDocumentation"\n }\n ]\n },\n "systemMetadata": {\n "properties": {\n "appSource": "ui"\n },\n "version": "1",\n "lastObserved": 0,\n "lastRunId": "no-run-id-provided",\n "runId": "no-run-id-provided"\n }\n }\n }\n]\n')),(0,a.yg)("p",null,"Next let's mutate ",(0,a.yg)("inlineCode",{parentName:"p"},"globalTags")," for the second URN by adding a new tag. This will increment the version of\nthe ",(0,a.yg)("inlineCode",{parentName:"p"},"globalTags")," aspect. The response will then look at like the following, notice the incremented\n",(0,a.yg)("inlineCode",{parentName:"p"},'"version": "2"')," in ",(0,a.yg)("inlineCode",{parentName:"p"},"systemMetadata")," for the ",(0,a.yg)("inlineCode",{parentName:"p"},"globalTags")," aspect. Also notice that there are now 2 tags present, unlike\npreviously where only ",(0,a.yg)("inlineCode",{parentName:"p"},"urn:li:tag:NeedsDocumentation")," was present."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'[\n {\n "urn": "urn:li:dataset:(urn:li:dataPlatform:hive,fct_users_deleted,PROD)",\n "datasetProperties": {\n "value": {\n "description": "table containing all the users deleted on a single day",\n "customProperties": {\n "encoding": "utf-8"\n },\n "tags": []\n },\n "systemMetadata": {\n "properties": {\n "clientVersion": "1!0.0.0.dev0",\n "clientId": "acryl-datahub"\n },\n "version": "1",\n "lastObserved": 1720781548776,\n "lastRunId": "file-2024_07_12-05_52_28",\n "runId": "file-2024_07_12-05_52_28"\n }\n }\n },\n {\n "urn": "urn:li:dataset:(urn:li:dataPlatform:hive,fct_users_created,PROD)",\n "datasetProperties": {\n "value": {\n "description": "table containing all the users created on a single day",\n "customProperties": {\n "encoding": "utf-8"\n },\n "tags": []\n },\n "systemMetadata": {\n "properties": {\n "clientVersion": "1!0.0.0.dev0",\n "clientId": "acryl-datahub"\n },\n "version": "1",\n "lastObserved": 1720781548773,\n "lastRunId": "file-2024_07_12-05_52_28",\n "runId": "file-2024_07_12-05_52_28"\n }\n },\n "globalTags": {\n "value": {\n "tags": [\n {\n "tag": "urn:li:tag:NeedsDocumentation"\n },\n {\n "tag": "urn:li:tag:Legacy"\n }\n ]\n },\n "systemMetadata": {\n "properties": {\n "appSource": "ui"\n },\n "version": "2",\n "lastObserved": 0,\n "lastRunId": "no-run-id-provided",\n "runId": "no-run-id-provided"\n }\n }\n }\n]\n')),(0,a.yg)("p",null,"Next, we'll retrieve the previous version of the ",(0,a.yg)("inlineCode",{parentName:"p"},"globalTags")," for the one aspect with a version 2 with the following query.\nWe can do this by populating the ",(0,a.yg)("inlineCode",{parentName:"p"},"headers")," map with ",(0,a.yg)("inlineCode",{parentName:"p"},"If-Version-Match")," to retrieve the previous version 1."),(0,a.yg)("p",null,"Example Request:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'[\n {\n "urn": "urn:li:dataset:(urn:li:dataPlatform:hive,fct_users_created,PROD)",\n "globalTags": {\n "headers": {\n "If-Version-Match": "1"\n }\n }\n }\n]\n')),(0,a.yg)("p",null,"Example Response:"),(0,a.yg)("p",null,"The previous version ",(0,a.yg)("inlineCode",{parentName:"p"},"1")," of the ",(0,a.yg)("inlineCode",{parentName:"p"},"globalTags")," aspect is returned as expected with only the single tag."),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'[\n {\n "urn": "urn:li:dataset:(urn:li:dataPlatform:hive,fct_users_created,PROD)",\n "globalTags": {\n "value": {\n "tags": [\n {\n "tag": "urn:li:tag:NeedsDocumentation"\n }\n ]\n },\n "systemMetadata": {\n "properties": {\n "appSource": "ui"\n },\n "version": "1",\n "lastObserved": 0,\n "lastRunId": "no-run-id-provided",\n "runId": "no-run-id-provided"\n }\n }\n }\n]\n')),(0,a.yg)("h3",{id:"generic-patching"},"Generic Patching"),(0,a.yg)("p",null,"The OpenAPI v3 PATCH endpoints offer advantages over previous patch support by removing the need for specific backend\ncode to handle patching, see ",(0,a.yg)("a",{parentName:"p",href:"/docs/advanced/patch#implementation-details"},"Template Classes"),". This technique leverages\nthe natural JSON structure of aspects and extends a generic patching mechanism based on the JSON Patch standard (RFC 6902)\nwith significant enhancements for array operations."),(0,a.yg)("p",null,"Note that the traditional patching templates are used by default in order to maintain backwards compatibility. The generic\npatching is activated if ",(0,a.yg)("inlineCode",{parentName:"p"},"arrayPrimaryKeys")," is non-empty or ",(0,a.yg)("inlineCode",{parentName:"p"},"forceGenericPatch")," is set to ",(0,a.yg)("inlineCode",{parentName:"p"},"true"),"."),(0,a.yg)("h4",{id:"advanced-json-patch-for-arrays"},"Advanced JSON Patch for Arrays"),(0,a.yg)("p",null,"Standard JSON Patch Limitations:"),(0,a.yg)("p",null,"The JSON Patch standard allows for array modifications primarily through index-based operations:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"add/[index]"),": Insert at specific position"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"remove/[index]"),": Remove element at position"),(0,a.yg)("li",{parentName:"ul"},(0,a.yg)("inlineCode",{parentName:"li"},"replace/[index]"),": Replace element at position")),(0,a.yg)("p",null,"This approach becomes problematic when:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Array ordering is unpredictable or may change"),(0,a.yg)("li",{parentName:"ul"},"Multiple clients are modifying the same resource concurrently"),(0,a.yg)("li",{parentName:"ul"},"The client doesn't have knowledge of current array indexes")),(0,a.yg)("p",null,"Key Concept: Array Primary Keys:"),(0,a.yg)("p",null,"DataHub extends the JSON Patch standard with the ",(0,a.yg)("inlineCode",{parentName:"p"},"arrayPrimaryKeys")," field that transforms array operations into map-like\noperations:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Arrays are conceptually treated as maps where each element can be addressed by its primary key"),(0,a.yg)("li",{parentName:"ul"},"Primary keys can be composite (multiple fields combined)"),(0,a.yg)("li",{parentName:"ul"},"Path expressions use these keys instead of numeric indexes"),(0,a.yg)("li",{parentName:"ul"},"Backend handles the conversion between the map-like operations and actual array modifications")),(0,a.yg)("p",null,"This approach allows for:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Idempotent operations regardless of array order"),(0,a.yg)("li",{parentName:"ul"},"Targeted modifications without needing to know current array state"),(0,a.yg)("li",{parentName:"ul"},"Concurrent updates without conflicts (when modifying different array elements)"),(0,a.yg)("li",{parentName:"ul"},"More intuitive and maintainable API usage")),(0,a.yg)("h4",{id:"primary-key-definition-and-path-construction"},"Primary Key Definition and Path Construction"),(0,a.yg)("p",null,"In this section lets take a look at a common patch use-case, adding/removing global tags while considering an\nattribution source for the tag. The following examples are specifically modifying the ",(0,a.yg)("inlineCode",{parentName:"p"},"globalTags")," aspect."),(0,a.yg)("p",null,"Defining Primary Keys:"),(0,a.yg)("p",null,"The ",(0,a.yg)("inlineCode",{parentName:"p"},"arrayPrimaryKeys")," property specifies which fields uniquely identify each array element:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "arrayPrimaryKeys": {\n "tags": ["attribution\u241fsource", "tag"]\n },\n "patch": [\n {\n "op": "add",\n "path": "/tags/urn:li:platformResource:source1/urn:li:tag:tag1",\n "value": {\n "tag": "urn:li:tag:tag1",\n "attribution": {\n "source": "urn:li:platformResource:source1",\n "actor": "urn:li:corpuser:user",\n "time": 0\n }\n }\n }\n ]\n}\n')),(0,a.yg)("p",null,"In this example:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"tags is the array field being patched"),(0,a.yg)("li",{parentName:"ul"},"The primary key is a composite of attribution.source and tag"),(0,a.yg)("li",{parentName:"ul"},"The ",(0,a.yg)("inlineCode",{parentName:"li"},"\u241f"),', "Unit Separator" (U+241F), delimiter indicates a nested path in the first key component')),(0,a.yg)("p",null,"Path Construction:"),(0,a.yg)("p",null,"When the backend processes a patch operation, it:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Converts the array to a map using the specified primary key fields"),(0,a.yg)("li",{parentName:"ul"},"Processes the operation against this map representation"),(0,a.yg)("li",{parentName:"ul"},"Converts the map back to an array for storage")),(0,a.yg)("p",null,"For example, with the path:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-text"},"/tags/urn:li:platformResource:source1/urn:li:tag:tag1\n")),(0,a.yg)("p",null,"The system:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Identifies tags as the target array"),(0,a.yg)("li",{parentName:"ul"},"Uses ",(0,a.yg)("inlineCode",{parentName:"li"},"urn:li:platformResource:source1")," as the value for ",(0,a.yg)("inlineCode",{parentName:"li"},"attribution.source")),(0,a.yg)("li",{parentName:"ul"},"Uses ",(0,a.yg)("inlineCode",{parentName:"li"},"urn:li:tag:tag1")," as the value for the tag"),(0,a.yg)("li",{parentName:"ul"},"Finds the matching array element(s) with these key values")),(0,a.yg)("p",null,"Supported Operations:"),(0,a.yg)("p",null,"The implementation supports standard JSON Patch operations:"),(0,a.yg)("table",null,(0,a.yg)("thead",{parentName:"table"},(0,a.yg)("tr",{parentName:"thead"},(0,a.yg)("th",{parentName:"tr",align:null},"Operation"),(0,a.yg)("th",{parentName:"tr",align:null},"Description"))),(0,a.yg)("tbody",{parentName:"table"},(0,a.yg)("tr",{parentName:"tbody"},(0,a.yg)("td",{parentName:"tr",align:null},"add"),(0,a.yg)("td",{parentName:"tr",align:null},"Add a new element or replace if exists")),(0,a.yg)("tr",{parentName:"tbody"},(0,a.yg)("td",{parentName:"tr",align:null},"remove"),(0,a.yg)("td",{parentName:"tr",align:null},"Remove an element matching keys")))),(0,a.yg)("h4",{id:"patch-operation-examples"},"Patch Operation Examples"),(0,a.yg)("p",null,"Adding Tagged Elements with Attribution:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "op": "add",\n "path": "/tags/urn:li:platformResource:source1/urn:li:tag:tag1",\n "value": {\n "tag": "urn:li:tag:tag1",\n "attribution": {\n "source": "urn:li:platformResource:source1",\n "actor": "urn:li:corpuser:user",\n "time": 0\n }\n }\n}\n')),(0,a.yg)("p",null,"This operation:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Checks if an element with matching keys exists in the array"),(0,a.yg)("li",{parentName:"ul"},"If not found, adds the new element"),(0,a.yg)("li",{parentName:"ul"},"If found, replaces the existing element")),(0,a.yg)("p",null,"Selective Removal:"),(0,a.yg)("pre",null,(0,a.yg)("code",{parentName:"pre",className:"language-json"},'{\n "op": "remove",\n "path": "/tags/urn:li:platformResource:source1/urn:li:tag:tag1"\n}\n')),(0,a.yg)("p",null,"This operation:"),(0,a.yg)("ul",null,(0,a.yg)("li",{parentName:"ul"},"Finds elements matching the composite key"),(0,a.yg)("li",{parentName:"ul"},"Removes only those elements"),(0,a.yg)("li",{parentName:"ul"},"Preserves other elements, even with partially matching keys")))}g.isMDXComponent=!0}}]);