From bad20b279c30b8982f951b55c0b5d98ba3cac985 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 4 Nov 2020 11:50:46 +0100 Subject: [PATCH 01/18] Start DBT module for running normalization transformations --- .../normalization/dbt-transform/README.md | 15 + .../dbt-transform/dbt_project.yml | 33 + .../dbt-transform/models/recipes_test.sql | 7 + .../dbt-transform/models/sources.yml | 7 + .../normalization/normalization.ipynb | 5984 +++++++++++++++++ .../normalization/normalization.txt | 264 + .../dbt-transform/sample_files/catalog.json | 514 ++ .../sample_files/catalog_exchangerateapi.json | 46 + .../sample_files/catalog_file.json | 34 + .../sample_files/catalog_github.json | 169 + .../sample_files/catalog_google-sheets.json | 46 + .../sample_files/catalog_hubspot.json | 79 + .../sample_files/catalog_salesforce.json | 22 + .../sample_files/catalog_stripe.json | 863 +++ .../sample_files/one_line_recipe.json | 1 + .../sample_files/one_recipe.json | 108 + .../dbt-transform/sample_files/profiles.yml | 40 + .../dbt-transform/sample_files/recipes.json | 10 + 18 files changed, 8242 insertions(+) create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/README.md create mode 100755 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/dbt_project.yml create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/recipes_test.sql create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/sources.yml create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.ipynb create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.txt create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog.json create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_exchangerateapi.json create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_file.json create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_github.json create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_google-sheets.json create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_hubspot.json create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_salesforce.json create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_stripe.json create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_line_recipe.json create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_recipe.json create mode 100755 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/profiles.yml create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/recipes.json diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/README.md b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/README.md new file mode 100644 index 0000000000000..4336065f3ab23 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/README.md @@ -0,0 +1,15 @@ +## Installing DBT + +1. Activate your venv and run `pip3 install dbt` +1. Copy `airbyte-normalization/sample_files/profiles.yml` over to `~/.dbt/profiles.yml` +1. Edit to configure your profiles accordingly + +## Running DBT + +1. `cd airbyte-normalization` +1. You can now run DBT commands, to check the setup is fine: `dbt debug` +1. To build the DBT tables in your warehouse: `dbt run` + +Note that in order to work with the current models that i am testing, you should have: + - `recipes` and `recipes_json` tables + - in a `data` dataset in your bigquery project (referenced in your `profiles.yml`... \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/dbt_project.yml b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/dbt_project.yml new file mode 100755 index 0000000000000..1edbf53543b16 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/dbt_project.yml @@ -0,0 +1,33 @@ +# Name your package! Package names should contain only lowercase characters +# and underscores. A good package name should reflect your organization's +# name or the intended use of these models +name: 'airbyte' +version: '1.0' +config-version: 2 + +# This setting configures which "profile" dbt uses for this project. Profiles contain +# database connection information, and should be configured in the ~/.dbt/profiles.yml file +profile: 'dev' + +# These configurations specify where dbt should look for different types of files. +# The `source-paths` config, for example, states that source models can be found +# in the "models/" directory. You probably won't need to change these! +source-paths: ["models"] +docs-paths: ["docs"] +analysis-paths: ["analysis"] +test-paths: ["tests"] +data-paths: ["data"] +macro-paths: ["macros"] + +target-path: "build" # directory which will store compiled SQL files +clean-targets: # directories to be removed by `dbt clean` + - "build" + - "dbt_modules" + +# You can define configurations for models in the `source-paths` directory here. +# Using these configurations, you can enable or disable models, change how they +# are materialized, and more! +models: + airbyte: + +schema: normalization + +materialized: view \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/recipes_test.sql b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/recipes_test.sql new file mode 100644 index 0000000000000..09701a9c533f1 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/recipes_test.sql @@ -0,0 +1,7 @@ +SELECT + * +FROM + {{ SOURCE( + 'data', + 'recipes_json' + ) }} \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/sources.yml b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/sources.yml new file mode 100644 index 0000000000000..1277e01279f6c --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/sources.yml @@ -0,0 +1,7 @@ +version: 2 + +sources: + - name: data + tables: + - name: recipes + - name: recipes_json \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.ipynb b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.ipynb new file mode 100644 index 0000000000000..9b0fa595cf9ea --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.ipynb @@ -0,0 +1,5984 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import json" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "From catalog ../sample_files/catalog.json:\n", + "--------------------\n", + "{\n", + " \"streams\": [\n", + " {\n", + " \"json_schema\": {\n", + " \"$id\": \"http://example.com/example.json\",\n", + " \"$schema\": \"http://json-schema.org/draft-07/schema\",\n", + " \"additionalProperties\": true,\n", + " \"default\": {},\n", + " \"description\": \"The root schema comprises the entire JSON document.\",\n", + " \"examples\": [\n", + " {\n", + " \"Author\": \"Mary Cadogan\",\n", + " \"Description\": \"Combine a few key Christmas flavours here to make a pie that both children and adults will adore\",\n", + " \"Ingredients\": [\n", + " {\n", + " \"ingredient\": \"olive oil\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 119,\n", + " \"fat\": \"13.5g\"\n", + " },\n", + " \"quantity\": \"2 tbsp\"\n", + " },\n", + " {\n", + " \"ingredient\": \"butter\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 102,\n", + " \"fat\": \"11.5g\"\n", + " },\n", + " \"quantity\": \"knob\"\n", + " },\n", + " {\n", + " \"ingredient\": \"onion\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 102,\n", + " \"fat\": \"11.5g\"\n", + " },\n", + " \"preparation\": \"finely chopped\",\n", + " \"quantity\": \"1\"\n", + " },\n", + " {\n", + " \"ingredient\": \"sausagemeat or skinned sausages\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 268,\n", + " \"fat\": \"18g\"\n", + " },\n", + " \"quantity\": \"500g\"\n", + " },\n", + " {\n", + " \"ingredient\": \"lemon\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 13\n", + " },\n", + " \"preparation\": \"grated zest\",\n", + " \"quantity\": \"1\"\n", + " },\n", + " {\n", + " \"ingredient\": \"fresh white breadcrumbs\",\n", + " \"quantity\": \"100g\"\n", + " },\n", + " {\n", + " \"ingredient\": \"ready-to-eat dried apricots\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 34,\n", + " \"fat\": \"0.27g\"\n", + " },\n", + " \"preparation\": \"chopped\",\n", + " \"quantity\": \"85g\"\n", + " },\n", + " {\n", + " \"ingredient\": \"chestnut, canned or vacuum-packed\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 77,\n", + " \"fat\": \"1g\"\n", + " },\n", + " \"preparation\": \"chopped\",\n", + " \"quantity\": \"58g\"\n", + " },\n", + " {\n", + " \"ingredient\": \"fresh or dried thyme\",\n", + " \"preparation\": \"chopped\",\n", + " \"quantity\": \"2 tsp\"\n", + " },\n", + " {\n", + " \"ingredient\": \"cranberries, fresh or frozen\",\n", + " \"quantity\": \"100g\"\n", + " },\n", + " {\n", + " \"ingredient\": \"boneless, skinless chicken breasts\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 284,\n", + " \"fat\": \"6.2g\"\n", + " },\n", + " \"quantity\": \"500g\"\n", + " },\n", + " {\n", + " \"ingredient\": \"pack ready-made shortcrust pastry\",\n", + " \"quantity\": \"500g\"\n", + " },\n", + " {\n", + " \"ingredient\": \"beaten egg\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 75,\n", + " \"fat\": \"5g\"\n", + " },\n", + " \"preparation\": \"to glaze\",\n", + " \"quantity\": \"1\"\n", + " }\n", + " ],\n", + " \"Method\": [\n", + " \"Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.\",\n", + " \"Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.\",\n", + " \"Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.\",\n", + " \"Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.\",\n", + " \"Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles.\"\n", + " ],\n", + " \"Name\": \"Christmas pie\",\n", + " \"url\": \"https://www.bbcgoodfood.com/recipes/2793/christmas-pie\"\n", + " }\n", + " ],\n", + " \"properties\": {\n", + " \"Author\": {\n", + " \"$id\": \"#/properties/Author\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"Mary Cadogan\"\n", + " ],\n", + " \"title\": \"The Author schema\",\n", + " \"type\": \"string\"\n", + " },\n", + " \"Description\": {\n", + " \"$id\": \"#/properties/Description\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"Combine a few key Christmas flavours here to make a pie that both children and adults will adore\"\n", + " ],\n", + " \"title\": \"The Description schema\",\n", + " \"type\": \"string\"\n", + " },\n", + " \"Ingredients\": {\n", + " \"$id\": \"#/properties/Ingredients\",\n", + " \"additionalItems\": true,\n", + " \"default\": [],\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " [\n", + " {\n", + " \"ingredient\": \"olive oil\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 119,\n", + " \"fat\": \"13.5g\"\n", + " },\n", + " \"quantity\": \"2 tbsp\"\n", + " },\n", + " {\n", + " \"ingredient\": \"butter\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 102,\n", + " \"fat\": \"11.5g\"\n", + " },\n", + " \"quantity\": \"knob\"\n", + " }\n", + " ]\n", + " ],\n", + " \"items\": {\n", + " \"$id\": \"#/properties/Ingredients/items\",\n", + " \"anyOf\": [\n", + " {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/0\",\n", + " \"additionalProperties\": true,\n", + " \"default\": {},\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " {\n", + " \"ingredient\": \"olive oil\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 119,\n", + " \"fat\": \"13.5g\"\n", + " },\n", + " \"quantity\": \"2 tbsp\"\n", + " }\n", + " ],\n", + " \"properties\": {\n", + " \"ingredient\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/0/properties/ingredient\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"olive oil\"\n", + " ],\n", + " \"title\": \"The ingredient schema\",\n", + " \"type\": \"string\"\n", + " },\n", + " \"nutritionfacts\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts\",\n", + " \"additionalProperties\": true,\n", + " \"default\": {},\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " {\n", + " \"calories\": 119,\n", + " \"fat\": \"13.5g\"\n", + " }\n", + " ],\n", + " \"properties\": {\n", + " \"calories\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/calories\",\n", + " \"default\": 0,\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " 119\n", + " ],\n", + " \"title\": \"The calories schema\",\n", + " \"type\": \"integer\"\n", + " },\n", + " \"fat\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/fat\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"13.5g\"\n", + " ],\n", + " \"title\": \"The fat schema\",\n", + " \"type\": \"string\"\n", + " }\n", + " },\n", + " \"required\": [\n", + " \"calories\",\n", + " \"fat\"\n", + " ],\n", + " \"title\": \"The nutritionfacts schema\",\n", + " \"type\": \"object\"\n", + " },\n", + " \"quantity\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/0/properties/quantity\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"2 tbsp\"\n", + " ],\n", + " \"title\": \"The quantity schema\",\n", + " \"type\": \"string\"\n", + " }\n", + " },\n", + " \"required\": [\n", + " \"quantity\",\n", + " \"ingredient\",\n", + " \"nutritionfacts\"\n", + " ],\n", + " \"title\": \"The first anyOf schema\",\n", + " \"type\": \"object\"\n", + " },\n", + " {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/1\",\n", + " \"additionalProperties\": true,\n", + " \"default\": {},\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " {\n", + " \"ingredient\": \"onion\",\n", + " \"nutritionfacts\": {\n", + " \"calories\": 102,\n", + " \"fat\": \"11.5g\"\n", + " },\n", + " \"preparation\": \"finely chopped\",\n", + " \"quantity\": \"1\"\n", + " }\n", + " ],\n", + " \"properties\": {\n", + " \"ingredient\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/1/properties/ingredient\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"onion\"\n", + " ],\n", + " \"title\": \"The ingredient schema\",\n", + " \"type\": \"string\"\n", + " },\n", + " \"nutritionfacts\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts\",\n", + " \"additionalProperties\": true,\n", + " \"default\": {},\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " {\n", + " \"calories\": 102,\n", + " \"fat\": \"11.5g\"\n", + " }\n", + " ],\n", + " \"properties\": {\n", + " \"calories\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/calories\",\n", + " \"default\": 0,\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " 102\n", + " ],\n", + " \"title\": \"The calories schema\",\n", + " \"type\": \"integer\"\n", + " },\n", + " \"fat\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/fat\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"11.5g\"\n", + " ],\n", + " \"title\": \"The fat schema\",\n", + " \"type\": \"string\"\n", + " }\n", + " },\n", + " \"required\": [\n", + " \"calories\",\n", + " \"fat\"\n", + " ],\n", + " \"title\": \"The nutritionfacts schema\",\n", + " \"type\": \"object\"\n", + " },\n", + " \"preparation\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/1/properties/preparation\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"finely chopped\"\n", + " ],\n", + " \"title\": \"The preparation schema\",\n", + " \"type\": \"string\"\n", + " },\n", + " \"quantity\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/1/properties/quantity\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"1\"\n", + " ],\n", + " \"title\": \"The quantity schema\",\n", + " \"type\": \"string\"\n", + " }\n", + " },\n", + " \"required\": [\n", + " \"quantity\",\n", + " \"ingredient\",\n", + " \"preparation\",\n", + " \"nutritionfacts\"\n", + " ],\n", + " \"title\": \"The second anyOf schema\",\n", + " \"type\": \"object\"\n", + " },\n", + " {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/2\",\n", + " \"additionalProperties\": true,\n", + " \"default\": {},\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " {\n", + " \"ingredient\": \"fresh white breadcrumbs\",\n", + " \"quantity\": \"100g\"\n", + " }\n", + " ],\n", + " \"properties\": {\n", + " \"ingredient\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/2/properties/ingredient\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"fresh white breadcrumbs\"\n", + " ],\n", + " \"title\": \"The ingredient schema\",\n", + " \"type\": \"string\"\n", + " },\n", + " \"quantity\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/2/properties/quantity\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"100g\"\n", + " ],\n", + " \"title\": \"The quantity schema\",\n", + " \"type\": \"string\"\n", + " }\n", + " },\n", + " \"required\": [\n", + " \"quantity\",\n", + " \"ingredient\"\n", + " ],\n", + " \"title\": \"The third anyOf schema\",\n", + " \"type\": \"object\"\n", + " },\n", + " {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/3\",\n", + " \"additionalProperties\": true,\n", + " \"default\": {},\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " {\n", + " \"ingredient\": \"fresh or dried thyme\",\n", + " \"preparation\": \"chopped\",\n", + " \"quantity\": \"2 tsp\"\n", + " }\n", + " ],\n", + " \"properties\": {\n", + " \"ingredient\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/3/properties/ingredient\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"fresh or dried thyme\"\n", + " ],\n", + " \"title\": \"The ingredient schema\",\n", + " \"type\": \"string\"\n", + " },\n", + " \"preparation\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/3/properties/preparation\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"chopped\"\n", + " ],\n", + " \"title\": \"The preparation schema\",\n", + " \"type\": \"string\"\n", + " },\n", + " \"quantity\": {\n", + " \"$id\": \"#/properties/Ingredients/items/anyOf/3/properties/quantity\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"2 tsp\"\n", + " ],\n", + " \"title\": \"The quantity schema\",\n", + " \"type\": \"string\"\n", + " }\n", + " },\n", + " \"required\": [\n", + " \"quantity\",\n", + " \"ingredient\",\n", + " \"preparation\"\n", + " ],\n", + " \"title\": \"The fourth anyOf schema\",\n", + " \"type\": \"object\"\n", + " }\n", + " ]\n", + " },\n", + " \"title\": \"The Ingredients schema\",\n", + " \"type\": \"array\"\n", + " },\n", + " \"Method\": {\n", + " \"$id\": \"#/properties/Method\",\n", + " \"additionalItems\": true,\n", + " \"default\": [],\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " [\n", + " \"Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.\",\n", + " \"Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.\"\n", + " ]\n", + " ],\n", + " \"items\": {\n", + " \"$id\": \"#/properties/Method/items\",\n", + " \"anyOf\": [\n", + " {\n", + " \"$id\": \"#/properties/Method/items/anyOf/0\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.\",\n", + " \"Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.\"\n", + " ],\n", + " \"title\": \"The first anyOf schema\",\n", + " \"type\": \"string\"\n", + " }\n", + " ]\n", + " },\n", + " \"title\": \"The Method schema\",\n", + " \"type\": \"array\"\n", + " },\n", + " \"Name\": {\n", + " \"$id\": \"#/properties/Name\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"Christmas pie\"\n", + " ],\n", + " \"title\": \"The Name schema\",\n", + " \"type\": \"string\"\n", + " },\n", + " \"url\": {\n", + " \"$id\": \"#/properties/url\",\n", + " \"default\": \"\",\n", + " \"description\": \"An explanation about the purpose of this instance.\",\n", + " \"examples\": [\n", + " \"https://www.bbcgoodfood.com/recipes/2793/christmas-pie\"\n", + " ],\n", + " \"title\": \"The url schema\",\n", + " \"type\": \"string\"\n", + " }\n", + " },\n", + " \"required\": [\n", + " \"Name\",\n", + " \"url\",\n", + " \"Description\",\n", + " \"Author\",\n", + " \"Ingredients\",\n", + " \"Method\"\n", + " ],\n", + " \"title\": \"The root schema\",\n", + " \"type\": \"object\"\n", + " },\n", + " \"name\": \"one_recipe\"\n", + " }\n", + " ]\n", + "}\n", + "--------------------\n", + "From catalog ../sample_files/catalog_github.json:\n", + "--------------------\n", + "{\n", + " \"streams\": [\n", + " {\n", + " \"json_schema\": {\n", + " \"additionalProperties\": false,\n", + " \"properties\": {\n", + " \"_sdc_repository\": {\n", + " \"type\": [\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"comments_url\": {\n", + " \"description\": \"The URL to the commit's comments page\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"commit\": {\n", + " \"additionalProperties\": false,\n", + " \"properties\": {\n", + " \"author\": {\n", + " \"additionalProperties\": false,\n", + " \"properties\": {\n", + " \"date\": {\n", + " \"description\": \"The date the author committed the change\",\n", + " \"format\": \"date-time\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"email\": {\n", + " \"description\": \"The author's email\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"login\": {\n", + " \"description\": \"The author's login\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"name\": {\n", + " \"description\": \"The author's name\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"comment_count\": {\n", + " \"description\": \"The number of comments on the commit\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"committer\": {\n", + " \"additionalProperties\": false,\n", + " \"properties\": {\n", + " \"date\": {\n", + " \"description\": \"The date the committer committed the change\",\n", + " \"format\": \"date-time\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"email\": {\n", + " \"description\": \"The committer's email\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"login\": {\n", + " \"description\": \"The committer's login\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"name\": {\n", + " \"description\": \"The committer's name\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"message\": {\n", + " \"description\": \"The commit message\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"tree\": {\n", + " \"additionalProperties\": false,\n", + " \"properties\": {\n", + " \"sha\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"url\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"url\": {\n", + " \"description\": \"The URL to the commit\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"files\": {\n", + " \"items\": {\n", + " \"properties\": {\n", + " \"additions\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"number\"\n", + " ]\n", + " },\n", + " \"blob_url\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"changes\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"number\"\n", + " ]\n", + " },\n", + " \"deletions\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"number\"\n", + " ]\n", + " },\n", + " \"filename\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"patch\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"raw_url\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"status\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"array\"\n", + " ]\n", + " },\n", + " \"html_url\": {\n", + " \"description\": \"The HTML URL to the commit\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"id\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"parents\": {\n", + " \"items\": {\n", + " \"additionalProperties\": false,\n", + " \"properties\": {\n", + " \"html_url\": {\n", + " \"description\": \"The HTML URL to the parent commit\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"sha\": {\n", + " \"description\": \"The git hash of the parent commit\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"url\": {\n", + " \"description\": \"The URL to the parent commit\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"array\"\n", + " ]\n", + " },\n", + " \"pr_id\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"pr_number\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"sha\": {\n", + " \"description\": \"The git commit hash\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"url\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"name\": \"commits\"\n", + " }\n", + " ]\n", + "}\n", + "--------------------\n", + "From catalog ../sample_files/catalog_stripe.json:\n", + "--------------------\n", + "{\n", + " \"streams\": [\n", + " {\n", + " \"name\": \"customers\",\n", + " \"schema\": {\n", + " \"properties\": {\n", + " \"account_balance\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"cards\": {\n", + " \"items\": {\n", + " \"properties\": {\n", + " \"address_city\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_country\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_line1\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_line1_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_line2\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_state\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_zip\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_zip_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"brand\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"country\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"customer\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"cvc_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"dynamic_last4\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"exp_month\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"exp_year\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"fingerprint\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"funding\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"id\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"last4\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"metadata\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"object\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"tokenization_method\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"type\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"array\"\n", + " ]\n", + " },\n", + " \"created\": {\n", + " \"format\": \"date-time\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"currency\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"default_card\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"default_source\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"delinquent\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"boolean\"\n", + " ]\n", + " },\n", + " \"description\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"discount\": {\n", + " \"properties\": {\n", + " \"coupon\": {\n", + " \"properties\": {\n", + " \"amount_off\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"created\": {\n", + " \"format\": \"date-time\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"currency\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"duration\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"duration_in_months\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"id\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"livemode\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"boolean\"\n", + " ]\n", + " },\n", + " \"max_redemptions\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"metadata\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"object\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"percent_off\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"percent_off_precise\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"number\"\n", + " ]\n", + " },\n", + " \"redeem_by\": {\n", + " \"format\": \"date-time\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"times_redeemed\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"valid\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"boolean\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"customer\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"end\": {\n", + " \"format\": \"date-time\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"object\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"start\": {\n", + " \"format\": \"date-time\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"subscription\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"email\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"id\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"invoice_prefix\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"livemode\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"boolean\"\n", + " ]\n", + " },\n", + " \"metadata\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"object\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"shipping\": {\n", + " \"properties\": {\n", + " \"address\": {\n", + " \"properties\": {\n", + " \"city\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"country\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"line1\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"line2\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"postal_code\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"state\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"phone\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"sources\": {\n", + " \"anyOf\": [\n", + " {\n", + " \"items\": {\n", + " \"properties\": {\n", + " \"ach_credit_transfer\": {\n", + " \"properties\": {\n", + " \"account_number\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"bank_name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"fingerprint\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"refund_account_holder_name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"refund_account_holder_type\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"refund_account_number\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"refund_routing_number\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"routing_number\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"swift_code\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"address_city\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_country\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_line1\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_line1_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_line2\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_state\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_zip\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_zip_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"alipay\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"amount\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"bancontact\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"brand\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"card\": {\n", + " \"properties\": {\n", + " \"address_line1_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_zip_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"brand\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"country\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"cvc_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"dynamic_last4\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"exp_month\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"exp_year\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"fingerprint\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"funding\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"last4\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"three_d_secure\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"tokenization_method\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"type\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"client_secret\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"country\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"created\": {\n", + " \"format\": \"date-time\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"currency\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"customer\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"cvc_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"dynamic_last4\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"eps\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"exp_month\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"exp_year\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"fingerprint\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"flow\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"funding\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"id\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"ideal\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"last4\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"livemode\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"boolean\"\n", + " ]\n", + " },\n", + " \"metadata\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"multibanco\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"object\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"owner\": {\n", + " \"properties\": {\n", + " \"address\": {\n", + " \"properties\": {\n", + " \"city\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"country\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"line1\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"line2\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"postal_code\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"state\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"email\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"phone\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"verified_address\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"verified_email\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"verified_name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"verified_phone\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"receiver\": {\n", + " \"properties\": {\n", + " \"address\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"amount_charged\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"amount_received\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"amount_returned\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"refund_attributes_method\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"refund_attributes_status\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"redirect\": {\n", + " \"properties\": {\n", + " \"failure_reason\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"return_url\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"status\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"url\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"statement_descriptor\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"status\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"tokenization_method\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"type\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"usage\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"array\"\n", + " ]\n", + " },\n", + " {\n", + " \"properties\": {\n", + " \"ach_credit_transfer\": {\n", + " \"properties\": {\n", + " \"account_number\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"bank_name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"fingerprint\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"refund_account_holder_name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"refund_account_holder_type\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"refund_account_number\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"refund_routing_number\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"routing_number\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"swift_code\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"address_city\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_country\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_line1\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_line1_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_line2\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_state\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_zip\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_zip_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"alipay\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"amount\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"bancontact\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"brand\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"card\": {\n", + " \"properties\": {\n", + " \"address_line1_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"address_zip_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"brand\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"country\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"cvc_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"dynamic_last4\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"exp_month\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"exp_year\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"fingerprint\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"funding\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"last4\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"three_d_secure\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"tokenization_method\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"type\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"client_secret\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"country\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"created\": {\n", + " \"format\": \"date-time\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"currency\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"customer\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"cvc_check\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"dynamic_last4\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"eps\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"exp_month\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"exp_year\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"fingerprint\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"flow\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"funding\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"id\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"ideal\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"last4\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"livemode\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"boolean\"\n", + " ]\n", + " },\n", + " \"metadata\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"multibanco\": {\n", + " \"properties\": {},\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"object\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"owner\": {\n", + " \"properties\": {\n", + " \"address\": {\n", + " \"properties\": {\n", + " \"city\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"country\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"line1\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"line2\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"postal_code\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"state\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"email\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"phone\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"verified_address\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"verified_email\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"verified_name\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"verified_phone\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"receiver\": {\n", + " \"properties\": {\n", + " \"address\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"amount_charged\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"amount_received\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"amount_returned\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"integer\"\n", + " ]\n", + " },\n", + " \"refund_attributes_method\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"refund_attributes_status\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"redirect\": {\n", + " \"properties\": {\n", + " \"failure_reason\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"return_url\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"status\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"url\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " },\n", + " \"statement_descriptor\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"status\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"tokenization_method\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"type\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"usage\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " }\n", + " ]\n", + " },\n", + " \"subscriptions\": {\n", + " \"items\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"array\"\n", + " ]\n", + " },\n", + " \"tax_info\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"tax_info_verification\": {\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " },\n", + " \"updated\": {\n", + " \"format\": \"date-time\",\n", + " \"type\": [\n", + " \"null\",\n", + " \"string\"\n", + " ]\n", + " }\n", + " },\n", + " \"type\": [\n", + " \"null\",\n", + " \"object\"\n", + " ]\n", + " }\n", + " }\n", + " ]\n", + "}\n", + "--------------------\n" + ] + } + ], + "source": [ + "def load_catalog(file):\n", + " with open(file) as f:\n", + " catalog = json.load(f)\n", + " print(f\"From catalog {file}:\")\n", + " print(\"--------------------\")\n", + " print(json.dumps(catalog, sort_keys=True, indent=4))\n", + " print(\"--------------------\")\n", + " return catalog\n", + "\n", + "catalog = load_catalog(\"../sample_files/catalog.json\")\n", + "github = load_catalog(\"../sample_files/catalog_github.json\")\n", + "stripe = load_catalog(\"../sample_files/catalog_stripe.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "def is_string(property_type) -> bool:\n", + " return property_type == \"string\" or \"string\" in property_type\n", + "\n", + "def is_integer(property_type) -> bool:\n", + " return property_type == \"integer\" or \"integer\" in property_type\n", + "\n", + "def is_boolean(property_type) -> bool:\n", + " return property_type == \"boolean\" or \"boolean\" in property_type\n", + " \n", + "def is_array(property_type) -> bool:\n", + " return property_type == \"array\" or \"array\" in property_type\n", + "\n", + "def is_object(property_type) -> bool:\n", + " return property_type == \"object\" or \"object\" in property_type\n", + "\n", + "def find_combining_schema(properties: dict):\n", + " return set(properties).intersection(set([\"anyOf\", \"oneOf\", \"allOf\"]))\n", + " \n", + "def json_extract_base_property(path: str, json_col: str, name: str, definition: dict) -> str:\n", + " current = \".\".join([path, name])\n", + " if not \"type\" in definition:\n", + " return None\n", + " elif is_string(definition[\"type\"]):\n", + " return f\"cast(json_extract_scalar({json_col}, '{current}') as string) as {name}\"\n", + " elif is_integer(definition[\"type\"]):\n", + " return f\"cast(json_extract_scalar({json_col}, '{current}') as int64) as {name}\"\n", + " elif is_boolean(definition[\"type\"]):\n", + " return f\"cast(json_extract_scalar({json_col}, '{current}') as boolean) as {name}\"\n", + " else:\n", + " return None\n", + " \n", + "def json_extract_nested_property(path: str, json_col: str, name: str, definition: dict) -> str:\n", + " current = \".\".join([path, name]) \n", + " if definition == None or not \"type\" in definition:\n", + " return (None, None)\n", + " elif is_array(definition[\"type\"]):\n", + " return (\n", + " f\"json_extract_array({json_col}, '{current}') as {name}\",\n", + " f\"cross join unnest({name}) as {name}\"\n", + " )\n", + " elif is_object(definition[\"type\"]):\n", + " return (\n", + " f\"json_extract({json_col}, '{current}') as {name}\",\n", + " \"\"\n", + " )\n", + " else:\n", + " return (None, None)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "In File one_recipe.sql:\n", + "--------------------\n", + "with \n", + "one_recipe_node as (\n", + " select \n", + " \n", + " cast(json_extract_scalar(json_blob, '$.Name') as string) as Name,\n", + " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", + " cast(json_extract_scalar(json_blob, '$.Description') as string) as Description,\n", + " cast(json_extract_scalar(json_blob, '$.Author') as string) as Author\n", + " from `airbytesandbox.data.one_recipe_json`\n", + "),\n", + "one_recipe_with_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(Name as string), \"\"),\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(Description as string), \"\"),\n", + " coalesce(cast(Author as string), \"\")\n", + " ))) as _one_recipe_hashid\n", + " from one_recipe_node\n", + ")\n", + "select * from one_recipe_with_id\n", + "--------------------\n", + "In File one_recipe_Ingredients.sql:\n", + "--------------------\n", + "with \n", + "one_recipe_node as (\n", + " select \n", + " json_extract_array(json_blob, '$.Ingredients') as Ingredients,\n", + " cast(json_extract_scalar(json_blob, '$.Name') as string) as Name,\n", + " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", + " cast(json_extract_scalar(json_blob, '$.Description') as string) as Description,\n", + " cast(json_extract_scalar(json_blob, '$.Author') as string) as Author\n", + " from `airbytesandbox.data.one_recipe_json`\n", + "),\n", + "one_recipe_with_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(Name as string), \"\"),\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(Description as string), \"\"),\n", + " coalesce(cast(Author as string), \"\")\n", + " ))) as _one_recipe_hashid,\n", + " Ingredients\n", + " from one_recipe_node\n", + " cross join unnest(Ingredients) as Ingredients\n", + "),\n", + "one_recipe_Ingredients_node as (\n", + " select \n", + " _one_recipe_hashid as _one_recipe_foreign_hashid,\n", + " cast(json_extract_scalar(Ingredients, '$.quantity') as string) as quantity,\n", + " cast(json_extract_scalar(Ingredients, '$.ingredient') as string) as ingredient\n", + " from one_recipe_with_id\n", + "),\n", + "one_recipe_Ingredients_with_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(quantity as string), \"\"),\n", + " coalesce(cast(ingredient as string), \"\")\n", + " ))) as _one_recipe_Ingredients_hashid\n", + " from one_recipe_Ingredients_node\n", + ")\n", + "select * from one_recipe_Ingredients_with_id\n", + "--------------------\n", + "In File one_recipe_Ingredients_nutritionfacts.sql:\n", + "--------------------\n", + "with \n", + "one_recipe_node as (\n", + " select \n", + " json_extract_array(json_blob, '$.Ingredients') as Ingredients,\n", + " cast(json_extract_scalar(json_blob, '$.Name') as string) as Name,\n", + " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", + " cast(json_extract_scalar(json_blob, '$.Description') as string) as Description,\n", + " cast(json_extract_scalar(json_blob, '$.Author') as string) as Author\n", + " from `airbytesandbox.data.one_recipe_json`\n", + "),\n", + "one_recipe_with_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(Name as string), \"\"),\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(Description as string), \"\"),\n", + " coalesce(cast(Author as string), \"\")\n", + " ))) as _one_recipe_hashid,\n", + " Ingredients\n", + " from one_recipe_node\n", + " cross join unnest(Ingredients) as Ingredients\n", + "),\n", + "one_recipe_Ingredients_node as (\n", + " select \n", + " json_extract(Ingredients, '$.nutritionfacts') as nutritionfacts,\n", + " cast(json_extract_scalar(Ingredients, '$.quantity') as string) as quantity,\n", + " cast(json_extract_scalar(Ingredients, '$.ingredient') as string) as ingredient\n", + " from one_recipe_with_id\n", + "),\n", + "one_recipe_Ingredients_with_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(quantity as string), \"\"),\n", + " coalesce(cast(ingredient as string), \"\")\n", + " ))) as _one_recipe_Ingredients_hashid,\n", + " nutritionfacts\n", + " from one_recipe_Ingredients_node\n", + " \n", + "),\n", + "one_recipe_Ingredients_nutritionfacts_node as (\n", + " select \n", + " _one_recipe_Ingredients_hashid as _one_recipe_Ingredients_foreign_hashid,\n", + " cast(json_extract_scalar(nutritionfacts, '$.calories') as int64) as calories,\n", + " cast(json_extract_scalar(nutritionfacts, '$.fat') as string) as fat\n", + " from one_recipe_Ingredients_with_id\n", + "),\n", + "one_recipe_Ingredients_nutritionfacts_with_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(calories as string), \"\"),\n", + " coalesce(cast(fat as string), \"\")\n", + " ))) as _one_recipe_Ingredients_nutritionfacts_hashid\n", + " from one_recipe_Ingredients_nutritionfacts_node\n", + ")\n", + "select * from one_recipe_Ingredients_nutritionfacts_with_id\n", + "--------------------\n", + "In File one_recipe_Method.sql:\n", + "--------------------\n", + "with \n", + "one_recipe_node as (\n", + " select \n", + " json_extract_array(json_blob, '$.Method') as Method,\n", + " cast(json_extract_scalar(json_blob, '$.Name') as string) as Name,\n", + " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", + " cast(json_extract_scalar(json_blob, '$.Description') as string) as Description,\n", + " cast(json_extract_scalar(json_blob, '$.Author') as string) as Author\n", + " from `airbytesandbox.data.one_recipe_json`\n", + "),\n", + "one_recipe_with_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(Name as string), \"\"),\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(Description as string), \"\"),\n", + " coalesce(cast(Author as string), \"\")\n", + " ))) as _one_recipe_hashid,\n", + " Method\n", + " from one_recipe_node\n", + " cross join unnest(Method) as Method\n", + ")\n", + "select \n", + " _one_recipe_hashid as _one_recipe_foreign_hashid,\n", + " Method\n", + " from one_recipe_with_id\n", + "--------------------\n" + ] + } + ], + "source": [ + "def select_table(table: str, columns=\"*\"):\n", + " return f\"\\nselect {columns} from {table}\"\n", + "\n", + "def extract_node_properties(path: str, json_col: str, properties: dict) -> dict:\n", + " result = {}\n", + " if properties:\n", + " for field in properties.keys():\n", + " sql_field = json_extract_base_property(path=path, json_col=json_col, name=field, definition=properties[field])\n", + " if sql_field:\n", + " result[field] = sql_field \n", + " return result\n", + "\n", + "def find_properties_object(path: str, field: str, properties) -> dict: \n", + " if isinstance(properties, str) or isinstance(properties, int):\n", + " return None \n", + " else: \n", + " if \"items\" in properties:\n", + " return find_properties_object(path, field, properties[\"items\"])\n", + " elif \"properties\" in properties: \n", + " # we found a properties object\n", + " return {field: properties['properties']}\n", + " elif \"type\" in properties and json_extract_base_property(path=path, json_col=\"\", name=\"\", definition=properties): \n", + " # we found a basic type \n", + " return {field: None}\n", + " elif isinstance(properties, dict): \n", + " for key in properties.keys():\n", + " if not json_extract_base_property(path, \"\", key, properties[key]):\n", + " child = find_properties_object(path, key, properties[key])\n", + " if child:\n", + " return child\n", + " elif isinstance(properties, list): \n", + " for item in properties:\n", + " child = find_properties_object(path=path, field=field, properties=item)\n", + " if child:\n", + " return child \n", + " return None\n", + " \n", + "def extract_nested_properties(path: str, json_col: str, field: str, properties: dict) -> dict:\n", + " result = {}\n", + " if properties: \n", + " for key in properties.keys():\n", + " combining = find_combining_schema(properties[key])\n", + " if combining: \n", + " # skip combining schemas\n", + " for combo in combining:\n", + " found = find_properties_object(path=f\"{path}.{field}.{key}\", field=key, properties=properties[key][combo]) \n", + " result.update(found) \n", + " elif not \"type\" in properties[key]: \n", + " pass\n", + " elif is_array(properties[key]['type']): \n", + " combining = find_combining_schema(properties[key]['items'])\n", + " if combining:\n", + " # skip combining schemas\n", + " for combo in combining:\n", + " found = find_properties_object(path=f\"{path}.{key}\", field=key, properties=properties[key]['items'][combo])\n", + " result.update(found) \n", + " else:\n", + " found = find_properties_object(path=f\"{path}.{key}\", field=key, properties=properties[key]['items']) \n", + " result.update(found) \n", + " elif is_object(properties[key]['type']): \n", + " found = find_properties_object(path=f\"{path}.{key}\", field=key, properties=properties[key]) \n", + " result.update(found) \n", + " return result\n", + "\n", + "def process_node(path: str, json_col: str, name: str, properties: dict, from_table: str = \"\", previous=\"with \", inject_cols=\"\") -> dict:\n", + " result = {}\n", + " if previous == \"with \":\n", + " prefix = previous\n", + " else:\n", + " prefix = previous + \",\"\n", + " node_properties = extract_node_properties(path=path, json_col=json_col, properties=properties)\n", + " node_columns = ',\\n '.join([sql for sql in node_properties.values()])\n", + " # FIXME: use DBT macros to be cross_db compatible instead\n", + " hash_node_columns = 'coalesce(cast(' + ' as string), \"\"),\\n coalesce(cast('.join([column for column in node_properties.keys()]) + ' as string), \"\")' \n", + " node_sql = f\"\"\"{prefix}\n", + "{name}_node as (\n", + " select \n", + " {inject_cols}\n", + " {node_columns}\n", + " from {from_table}\n", + "),\n", + "{name}_with_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " {hash_node_columns}\n", + " ))) as _{name}_hashid\n", + " from {name}_node\n", + ")\"\"\"\n", + " # SQL Query for current node's basic properties\n", + " result[name] = node_sql + select_table(f\"{name}_with_id\")\n", + "\n", + " children_columns = extract_nested_properties(path=path, json_col=json_col, field=name, properties=properties)\n", + " if children_columns: \n", + " for col in children_columns.keys(): \n", + " child_col, join_child_table = json_extract_nested_property(path=path, json_col=json_col, name=col, definition=properties[col])\n", + " child_sql = f\"\"\"{prefix}\n", + "{name}_node as (\n", + " select \n", + " {child_col},\n", + " {node_columns}\n", + " from {from_table}\n", + "),\n", + "{name}_with_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " {hash_node_columns}\n", + " ))) as _{name}_hashid,\n", + " {col}\n", + " from {name}_node\n", + " {join_child_table}\n", + ")\"\"\"\n", + " if children_columns[col]:\n", + " children = process_node(path=\"$\", json_col=col, name=f\"{name}_{col}\", properties=children_columns[col], from_table=f\"{name}_with_id\", previous=child_sql, inject_cols=f\"_{name}_hashid as _{name}_foreign_hashid,\")\n", + " result.update(children)\n", + " else:\n", + " # SQL Query for current node's basic properties\n", + " result[f\"{name}_{col}\"] = child_sql + select_table(f\"{name}_with_id\", columns=f\"\"\"\n", + " _{name}_hashid as _{name}_foreign_hashid,\n", + " {col}\n", + "\"\"\")\n", + " return result\n", + "\n", + "def generate_dbt_model(catalog: dict, json_col: str, from_table: str) -> dict:\n", + " result = {}\n", + " for obj in catalog[\"streams\"]:\n", + " name = obj['name']\n", + " if \"json_schema\" in obj:\n", + " properties = obj['json_schema']['properties']\n", + " elif \"schema\" in obj:\n", + " properties = obj['schema']['properties']\n", + " result.update(process_node(path=\"$\", json_col=json_col, name=name, properties=properties, from_table=from_table))\n", + " return result\n", + "\n", + "def print_result(result):\n", + " for name in result.keys():\n", + " print(f\"In File {name}.sql:\")\n", + " print(\"--------------------\")\n", + " print(result[name])\n", + " print(\"--------------------\")\n", + "\n", + "print_result(generate_dbt_model(catalog=catalog, json_col=\"json_blob\", from_table=\"`airbytesandbox.data.one_recipe_json`\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "In File customers.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " \n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid\n", + " from customers_node\n", + ")\n", + "select * from customers_id\n", + "--------------------\n", + "In File customers_metadata.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " json_extract(json_blob, '$.metadata') as metadata,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " metadata\n", + " from customers_node\n", + " \n", + ")\n", + "select \n", + " _customers_hashid as _customers_foreign_hashid,\n", + " metadata\n", + " from customers_id\n", + "--------------------\n", + "In File customers_shipping.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " json_extract(json_blob, '$.shipping') as shipping,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " shipping\n", + " from customers_node\n", + " \n", + "),\n", + "customers_shipping_node as (\n", + " select \n", + " _customers_hashid as _customers_foreign_hashid,\n", + " cast(json_extract_scalar(shipping, '$.name') as string) as name,\n", + " cast(json_extract_scalar(shipping, '$.phone') as string) as phone\n", + " from customers_id\n", + "),\n", + "customers_shipping_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(phone as string), \"\")\n", + " ))) as _customers_shipping_hashid\n", + " from customers_shipping_node\n", + ")\n", + "select * from customers_shipping_id\n", + "--------------------\n", + "In File customers_shipping_address.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " json_extract(json_blob, '$.shipping') as shipping,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " shipping\n", + " from customers_node\n", + " \n", + "),\n", + "customers_shipping_node as (\n", + " select \n", + " json_extract(shipping, '$.address') as address,\n", + " cast(json_extract_scalar(shipping, '$.name') as string) as name,\n", + " cast(json_extract_scalar(shipping, '$.phone') as string) as phone\n", + " from customers_id\n", + "),\n", + "customers_shipping_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(phone as string), \"\")\n", + " ))) as _customers_shipping_hashid,\n", + " address\n", + " from customers_shipping_node\n", + " \n", + "),\n", + "customers_shipping_address_node as (\n", + " select \n", + " _customers_shipping_hashid as _customers_shipping_foreign_hashid,\n", + " cast(json_extract_scalar(address, '$.line2') as string) as line2,\n", + " cast(json_extract_scalar(address, '$.state') as string) as state,\n", + " cast(json_extract_scalar(address, '$.city') as string) as city,\n", + " cast(json_extract_scalar(address, '$.postal_code') as string) as postal_code,\n", + " cast(json_extract_scalar(address, '$.country') as string) as country,\n", + " cast(json_extract_scalar(address, '$.line1') as string) as line1\n", + " from customers_shipping_id\n", + "),\n", + "customers_shipping_address_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(line2 as string), \"\"),\n", + " coalesce(cast(state as string), \"\"),\n", + " coalesce(cast(city as string), \"\"),\n", + " coalesce(cast(postal_code as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(line1 as string), \"\")\n", + " ))) as _customers_shipping_address_hashid\n", + " from customers_shipping_address_node\n", + ")\n", + "select * from customers_shipping_address_id\n", + "--------------------\n", + "In File customers_sources.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " _customers_hashid as _customers_foreign_hashid,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid\n", + " from customers_sources_node\n", + ")\n", + "select * from customers_sources_id\n", + "--------------------\n", + "In File customers_sources_metadata.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " json_extract(sources, '$.metadata') as metadata,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid,\n", + " metadata\n", + " from customers_sources_node\n", + " \n", + ")\n", + "select \n", + " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", + " metadata\n", + " from customers_sources_id\n", + "--------------------\n", + "In File customers_sources_card.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " json_extract(sources, '$.card') as card,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid,\n", + " card\n", + " from customers_sources_node\n", + " \n", + "),\n", + "customers_sources_card_node as (\n", + " select \n", + " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", + " cast(json_extract_scalar(card, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(card, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(card, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(card, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(card, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(card, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(card, '$.name') as string) as name,\n", + " cast(json_extract_scalar(card, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(card, '$.three_d_secure') as string) as three_d_secure,\n", + " cast(json_extract_scalar(card, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(card, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(card, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(card, '$.country') as string) as country,\n", + " cast(json_extract_scalar(card, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(card, '$.type') as string) as type\n", + " from customers_sources_id\n", + "),\n", + "customers_sources_card_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(three_d_secure as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(type as string), \"\")\n", + " ))) as _customers_sources_card_hashid\n", + " from customers_sources_card_node\n", + ")\n", + "select * from customers_sources_card_id\n", + "--------------------\n", + "In File customers_sources_owner.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " json_extract(sources, '$.owner') as owner,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid,\n", + " owner\n", + " from customers_sources_node\n", + " \n", + "),\n", + "customers_sources_owner_node as (\n", + " select \n", + " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", + " cast(json_extract_scalar(owner, '$.verified_address') as string) as verified_address,\n", + " cast(json_extract_scalar(owner, '$.email') as string) as email,\n", + " cast(json_extract_scalar(owner, '$.verified_email') as string) as verified_email,\n", + " cast(json_extract_scalar(owner, '$.name') as string) as name,\n", + " cast(json_extract_scalar(owner, '$.phone') as string) as phone,\n", + " cast(json_extract_scalar(owner, '$.verified_name') as string) as verified_name,\n", + " cast(json_extract_scalar(owner, '$.verified_phone') as string) as verified_phone\n", + " from customers_sources_id\n", + "),\n", + "customers_sources_owner_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(verified_address as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(verified_email as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(phone as string), \"\"),\n", + " coalesce(cast(verified_name as string), \"\"),\n", + " coalesce(cast(verified_phone as string), \"\")\n", + " ))) as _customers_sources_owner_hashid\n", + " from customers_sources_owner_node\n", + ")\n", + "select * from customers_sources_owner_id\n", + "--------------------\n", + "In File customers_sources_owner_address.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " json_extract(sources, '$.owner') as owner,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid,\n", + " owner\n", + " from customers_sources_node\n", + " \n", + "),\n", + "customers_sources_owner_node as (\n", + " select \n", + " json_extract(owner, '$.address') as address,\n", + " cast(json_extract_scalar(owner, '$.verified_address') as string) as verified_address,\n", + " cast(json_extract_scalar(owner, '$.email') as string) as email,\n", + " cast(json_extract_scalar(owner, '$.verified_email') as string) as verified_email,\n", + " cast(json_extract_scalar(owner, '$.name') as string) as name,\n", + " cast(json_extract_scalar(owner, '$.phone') as string) as phone,\n", + " cast(json_extract_scalar(owner, '$.verified_name') as string) as verified_name,\n", + " cast(json_extract_scalar(owner, '$.verified_phone') as string) as verified_phone\n", + " from customers_sources_id\n", + "),\n", + "customers_sources_owner_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(verified_address as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(verified_email as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(phone as string), \"\"),\n", + " coalesce(cast(verified_name as string), \"\"),\n", + " coalesce(cast(verified_phone as string), \"\")\n", + " ))) as _customers_sources_owner_hashid,\n", + " address\n", + " from customers_sources_owner_node\n", + " \n", + "),\n", + "customers_sources_owner_address_node as (\n", + " select \n", + " _customers_sources_owner_hashid as _customers_sources_owner_foreign_hashid,\n", + " cast(json_extract_scalar(address, '$.line2') as string) as line2,\n", + " cast(json_extract_scalar(address, '$.state') as string) as state,\n", + " cast(json_extract_scalar(address, '$.city') as string) as city,\n", + " cast(json_extract_scalar(address, '$.postal_code') as string) as postal_code,\n", + " cast(json_extract_scalar(address, '$.country') as string) as country,\n", + " cast(json_extract_scalar(address, '$.line1') as string) as line1\n", + " from customers_sources_owner_id\n", + "),\n", + "customers_sources_owner_address_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(line2 as string), \"\"),\n", + " coalesce(cast(state as string), \"\"),\n", + " coalesce(cast(city as string), \"\"),\n", + " coalesce(cast(postal_code as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(line1 as string), \"\")\n", + " ))) as _customers_sources_owner_address_hashid\n", + " from customers_sources_owner_address_node\n", + ")\n", + "select * from customers_sources_owner_address_id\n", + "--------------------\n", + "In File customers_sources_receiver.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " json_extract(sources, '$.receiver') as receiver,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid,\n", + " receiver\n", + " from customers_sources_node\n", + " \n", + "),\n", + "customers_sources_receiver_node as (\n", + " select \n", + " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", + " cast(json_extract_scalar(receiver, '$.refund_attributes_method') as string) as refund_attributes_method,\n", + " cast(json_extract_scalar(receiver, '$.amount_returned') as int64) as amount_returned,\n", + " cast(json_extract_scalar(receiver, '$.amount_received') as int64) as amount_received,\n", + " cast(json_extract_scalar(receiver, '$.refund_attributes_status') as string) as refund_attributes_status,\n", + " cast(json_extract_scalar(receiver, '$.address') as string) as address,\n", + " cast(json_extract_scalar(receiver, '$.amount_charged') as int64) as amount_charged\n", + " from customers_sources_id\n", + "),\n", + "customers_sources_receiver_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(refund_attributes_method as string), \"\"),\n", + " coalesce(cast(amount_returned as string), \"\"),\n", + " coalesce(cast(amount_received as string), \"\"),\n", + " coalesce(cast(refund_attributes_status as string), \"\"),\n", + " coalesce(cast(address as string), \"\"),\n", + " coalesce(cast(amount_charged as string), \"\")\n", + " ))) as _customers_sources_receiver_hashid\n", + " from customers_sources_receiver_node\n", + ")\n", + "select * from customers_sources_receiver_id\n", + "--------------------\n", + "In File customers_sources_ach_credit_transfer.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " json_extract(sources, '$.ach_credit_transfer') as ach_credit_transfer,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid,\n", + " ach_credit_transfer\n", + " from customers_sources_node\n", + " \n", + "),\n", + "customers_sources_ach_credit_transfer_node as (\n", + " select \n", + " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", + " cast(json_extract_scalar(ach_credit_transfer, '$.bank_name') as string) as bank_name,\n", + " cast(json_extract_scalar(ach_credit_transfer, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(ach_credit_transfer, '$.routing_number') as string) as routing_number,\n", + " cast(json_extract_scalar(ach_credit_transfer, '$.swift_code') as string) as swift_code,\n", + " cast(json_extract_scalar(ach_credit_transfer, '$.refund_account_holder_type') as string) as refund_account_holder_type,\n", + " cast(json_extract_scalar(ach_credit_transfer, '$.refund_account_holder_name') as string) as refund_account_holder_name,\n", + " cast(json_extract_scalar(ach_credit_transfer, '$.refund_account_number') as string) as refund_account_number,\n", + " cast(json_extract_scalar(ach_credit_transfer, '$.refund_routing_number') as string) as refund_routing_number,\n", + " cast(json_extract_scalar(ach_credit_transfer, '$.account_number') as string) as account_number\n", + " from customers_sources_id\n", + "),\n", + "customers_sources_ach_credit_transfer_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(bank_name as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(routing_number as string), \"\"),\n", + " coalesce(cast(swift_code as string), \"\"),\n", + " coalesce(cast(refund_account_holder_type as string), \"\"),\n", + " coalesce(cast(refund_account_holder_name as string), \"\"),\n", + " coalesce(cast(refund_account_number as string), \"\"),\n", + " coalesce(cast(refund_routing_number as string), \"\"),\n", + " coalesce(cast(account_number as string), \"\")\n", + " ))) as _customers_sources_ach_credit_transfer_hashid\n", + " from customers_sources_ach_credit_transfer_node\n", + ")\n", + "select * from customers_sources_ach_credit_transfer_id\n", + "--------------------\n", + "In File customers_sources_alipay.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " json_extract(sources, '$.alipay') as alipay,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid,\n", + " alipay\n", + " from customers_sources_node\n", + " \n", + ")\n", + "select \n", + " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", + " alipay\n", + " from customers_sources_id\n", + "--------------------\n", + "In File customers_sources_bancontact.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " json_extract(sources, '$.bancontact') as bancontact,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid,\n", + " bancontact\n", + " from customers_sources_node\n", + " \n", + ")\n", + "select \n", + " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", + " bancontact\n", + " from customers_sources_id\n", + "--------------------\n", + "In File customers_sources_eps.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " json_extract(sources, '$.eps') as eps,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid,\n", + " eps\n", + " from customers_sources_node\n", + " \n", + ")\n", + "select \n", + " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", + " eps\n", + " from customers_sources_id\n", + "--------------------\n", + "In File customers_sources_ideal.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " json_extract(sources, '$.ideal') as ideal,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid,\n", + " ideal\n", + " from customers_sources_node\n", + " \n", + ")\n", + "select \n", + " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", + " ideal\n", + " from customers_sources_id\n", + "--------------------\n", + "In File customers_sources_multibanco.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " json_extract(sources, '$.multibanco') as multibanco,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid,\n", + " multibanco\n", + " from customers_sources_node\n", + " \n", + ")\n", + "select \n", + " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", + " multibanco\n", + " from customers_sources_id\n", + "--------------------\n", + "In File customers_sources_redirect.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " None,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " sources\n", + " from customers_node\n", + " None\n", + "),\n", + "customers_sources_node as (\n", + " select \n", + " json_extract(sources, '$.redirect') as redirect,\n", + " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", + " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", + " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", + " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", + " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", + " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", + " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", + " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", + " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", + " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", + " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", + " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", + " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", + " from customers_id\n", + "),\n", + "customers_sources_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(type as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(statement_descriptor as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(amount as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(usage as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(client_secret as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(flow as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\")\n", + " ))) as _customers_sources_hashid,\n", + " redirect\n", + " from customers_sources_node\n", + " \n", + "),\n", + "customers_sources_redirect_node as (\n", + " select \n", + " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", + " cast(json_extract_scalar(redirect, '$.failure_reason') as string) as failure_reason,\n", + " cast(json_extract_scalar(redirect, '$.return_url') as string) as return_url,\n", + " cast(json_extract_scalar(redirect, '$.status') as string) as status,\n", + " cast(json_extract_scalar(redirect, '$.url') as string) as url\n", + " from customers_sources_id\n", + "),\n", + "customers_sources_redirect_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(failure_reason as string), \"\"),\n", + " coalesce(cast(return_url as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(url as string), \"\")\n", + " ))) as _customers_sources_redirect_hashid\n", + " from customers_sources_redirect_node\n", + ")\n", + "select * from customers_sources_redirect_id\n", + "--------------------\n", + "In File customers_cards.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " json_extract_array(json_blob, '$.cards') as cards,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " cards\n", + " from customers_node\n", + " cross join unnest(cards) as cards\n", + "),\n", + "customers_cards_node as (\n", + " select \n", + " _customers_hashid as _customers_foreign_hashid,\n", + " cast(json_extract_scalar(cards, '$.object') as string) as object,\n", + " cast(json_extract_scalar(cards, '$.id') as string) as id,\n", + " cast(json_extract_scalar(cards, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(cards, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(cards, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(cards, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(cards, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(cards, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(cards, '$.country') as string) as country,\n", + " cast(json_extract_scalar(cards, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(cards, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(cards, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(cards, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(cards, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(cards, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(cards, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(cards, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(cards, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(cards, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(cards, '$.name') as string) as name,\n", + " cast(json_extract_scalar(cards, '$.address_state') as string) as address_state,\n", + " cast(json_extract_scalar(cards, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(cards, '$.type') as string) as type\n", + " from customers_id\n", + "),\n", + "customers_cards_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(type as string), \"\")\n", + " ))) as _customers_cards_hashid\n", + " from customers_cards_node\n", + ")\n", + "select * from customers_cards_id\n", + "--------------------\n", + "In File customers_cards_metadata.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " json_extract_array(json_blob, '$.cards') as cards,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " cards\n", + " from customers_node\n", + " cross join unnest(cards) as cards\n", + "),\n", + "customers_cards_node as (\n", + " select \n", + " json_extract(cards, '$.metadata') as metadata,\n", + " cast(json_extract_scalar(cards, '$.object') as string) as object,\n", + " cast(json_extract_scalar(cards, '$.id') as string) as id,\n", + " cast(json_extract_scalar(cards, '$.exp_month') as int64) as exp_month,\n", + " cast(json_extract_scalar(cards, '$.dynamic_last4') as string) as dynamic_last4,\n", + " cast(json_extract_scalar(cards, '$.exp_year') as int64) as exp_year,\n", + " cast(json_extract_scalar(cards, '$.last4') as string) as last4,\n", + " cast(json_extract_scalar(cards, '$.funding') as string) as funding,\n", + " cast(json_extract_scalar(cards, '$.brand') as string) as brand,\n", + " cast(json_extract_scalar(cards, '$.country') as string) as country,\n", + " cast(json_extract_scalar(cards, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(cards, '$.cvc_check') as string) as cvc_check,\n", + " cast(json_extract_scalar(cards, '$.address_line2') as string) as address_line2,\n", + " cast(json_extract_scalar(cards, '$.address_line1') as string) as address_line1,\n", + " cast(json_extract_scalar(cards, '$.fingerprint') as string) as fingerprint,\n", + " cast(json_extract_scalar(cards, '$.address_zip') as string) as address_zip,\n", + " cast(json_extract_scalar(cards, '$.address_city') as string) as address_city,\n", + " cast(json_extract_scalar(cards, '$.address_country') as string) as address_country,\n", + " cast(json_extract_scalar(cards, '$.address_line1_check') as string) as address_line1_check,\n", + " cast(json_extract_scalar(cards, '$.tokenization_method') as string) as tokenization_method,\n", + " cast(json_extract_scalar(cards, '$.name') as string) as name,\n", + " cast(json_extract_scalar(cards, '$.address_state') as string) as address_state,\n", + " cast(json_extract_scalar(cards, '$.address_zip_check') as string) as address_zip_check,\n", + " cast(json_extract_scalar(cards, '$.type') as string) as type\n", + " from customers_id\n", + "),\n", + "customers_cards_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(exp_month as string), \"\"),\n", + " coalesce(cast(dynamic_last4 as string), \"\"),\n", + " coalesce(cast(exp_year as string), \"\"),\n", + " coalesce(cast(last4 as string), \"\"),\n", + " coalesce(cast(funding as string), \"\"),\n", + " coalesce(cast(brand as string), \"\"),\n", + " coalesce(cast(country as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(cvc_check as string), \"\"),\n", + " coalesce(cast(address_line2 as string), \"\"),\n", + " coalesce(cast(address_line1 as string), \"\"),\n", + " coalesce(cast(fingerprint as string), \"\"),\n", + " coalesce(cast(address_zip as string), \"\"),\n", + " coalesce(cast(address_city as string), \"\"),\n", + " coalesce(cast(address_country as string), \"\"),\n", + " coalesce(cast(address_line1_check as string), \"\"),\n", + " coalesce(cast(tokenization_method as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(address_state as string), \"\"),\n", + " coalesce(cast(address_zip_check as string), \"\"),\n", + " coalesce(cast(type as string), \"\")\n", + " ))) as _customers_cards_hashid,\n", + " metadata\n", + " from customers_cards_node\n", + " \n", + ")\n", + "select \n", + " _customers_cards_hashid as _customers_cards_foreign_hashid,\n", + " metadata\n", + " from customers_cards_id\n", + "--------------------\n", + "In File customers_subscriptions.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " json_extract_array(json_blob, '$.subscriptions') as subscriptions,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " subscriptions\n", + " from customers_node\n", + " cross join unnest(subscriptions) as subscriptions\n", + ")\n", + "select \n", + " _customers_hashid as _customers_foreign_hashid,\n", + " subscriptions\n", + " from customers_id\n", + "--------------------\n", + "In File customers_discount.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " json_extract(json_blob, '$.discount') as discount,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " discount\n", + " from customers_node\n", + " \n", + "),\n", + "customers_discount_node as (\n", + " select \n", + " _customers_hashid as _customers_foreign_hashid,\n", + " cast(json_extract_scalar(discount, '$.end') as string) as end,\n", + " cast(json_extract_scalar(discount, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(discount, '$.start') as string) as start,\n", + " cast(json_extract_scalar(discount, '$.object') as string) as object,\n", + " cast(json_extract_scalar(discount, '$.subscription') as string) as subscription\n", + " from customers_id\n", + "),\n", + "customers_discount_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(end as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(start as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(subscription as string), \"\")\n", + " ))) as _customers_discount_hashid\n", + " from customers_discount_node\n", + ")\n", + "select * from customers_discount_id\n", + "--------------------\n", + "In File customers_discount_coupon.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " json_extract(json_blob, '$.discount') as discount,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " discount\n", + " from customers_node\n", + " \n", + "),\n", + "customers_discount_node as (\n", + " select \n", + " json_extract(discount, '$.coupon') as coupon,\n", + " cast(json_extract_scalar(discount, '$.end') as string) as end,\n", + " cast(json_extract_scalar(discount, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(discount, '$.start') as string) as start,\n", + " cast(json_extract_scalar(discount, '$.object') as string) as object,\n", + " cast(json_extract_scalar(discount, '$.subscription') as string) as subscription\n", + " from customers_id\n", + "),\n", + "customers_discount_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(end as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(start as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(subscription as string), \"\")\n", + " ))) as _customers_discount_hashid,\n", + " coupon\n", + " from customers_discount_node\n", + " \n", + "),\n", + "customers_discount_coupon_node as (\n", + " select \n", + " _customers_discount_hashid as _customers_discount_foreign_hashid,\n", + " cast(json_extract_scalar(coupon, '$.valid') as boolean) as valid,\n", + " cast(json_extract_scalar(coupon, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(coupon, '$.amount_off') as int64) as amount_off,\n", + " cast(json_extract_scalar(coupon, '$.redeem_by') as string) as redeem_by,\n", + " cast(json_extract_scalar(coupon, '$.duration_in_months') as int64) as duration_in_months,\n", + " cast(json_extract_scalar(coupon, '$.max_redemptions') as int64) as max_redemptions,\n", + " cast(json_extract_scalar(coupon, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(coupon, '$.name') as string) as name,\n", + " cast(json_extract_scalar(coupon, '$.times_redeemed') as int64) as times_redeemed,\n", + " cast(json_extract_scalar(coupon, '$.id') as string) as id,\n", + " cast(json_extract_scalar(coupon, '$.duration') as string) as duration,\n", + " cast(json_extract_scalar(coupon, '$.object') as string) as object,\n", + " cast(json_extract_scalar(coupon, '$.percent_off') as int64) as percent_off,\n", + " cast(json_extract_scalar(coupon, '$.created') as string) as created\n", + " from customers_discount_id\n", + "),\n", + "customers_discount_coupon_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(valid as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(amount_off as string), \"\"),\n", + " coalesce(cast(redeem_by as string), \"\"),\n", + " coalesce(cast(duration_in_months as string), \"\"),\n", + " coalesce(cast(max_redemptions as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(times_redeemed as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(duration as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(percent_off as string), \"\"),\n", + " coalesce(cast(created as string), \"\")\n", + " ))) as _customers_discount_coupon_hashid\n", + " from customers_discount_coupon_node\n", + ")\n", + "select * from customers_discount_coupon_id\n", + "--------------------\n", + "In File customers_discount_coupon_metadata.sql:\n", + "--------------------\n", + "with \n", + "customers_node as (\n", + " select \n", + " json_extract(json_blob, '$.discount') as discount,\n", + " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", + " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", + " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", + " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", + " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", + " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", + " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", + " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", + " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", + " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", + " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", + " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", + " from `airbytesandbox.data.stripe_json`\n", + "),\n", + "customers_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(delinquent as string), \"\"),\n", + " coalesce(cast(description as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(default_source as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(default_card as string), \"\"),\n", + " coalesce(cast(account_balance as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(invoice_prefix as string), \"\"),\n", + " coalesce(cast(tax_info_verification as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(created as string), \"\"),\n", + " coalesce(cast(tax_info as string), \"\"),\n", + " coalesce(cast(updated as string), \"\")\n", + " ))) as _customers_hashid,\n", + " discount\n", + " from customers_node\n", + " \n", + "),\n", + "customers_discount_node as (\n", + " select \n", + " json_extract(discount, '$.coupon') as coupon,\n", + " cast(json_extract_scalar(discount, '$.end') as string) as end,\n", + " cast(json_extract_scalar(discount, '$.customer') as string) as customer,\n", + " cast(json_extract_scalar(discount, '$.start') as string) as start,\n", + " cast(json_extract_scalar(discount, '$.object') as string) as object,\n", + " cast(json_extract_scalar(discount, '$.subscription') as string) as subscription\n", + " from customers_id\n", + "),\n", + "customers_discount_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(end as string), \"\"),\n", + " coalesce(cast(customer as string), \"\"),\n", + " coalesce(cast(start as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(subscription as string), \"\")\n", + " ))) as _customers_discount_hashid,\n", + " coupon\n", + " from customers_discount_node\n", + " \n", + "),\n", + "customers_discount_coupon_node as (\n", + " select \n", + " json_extract(coupon, '$.metadata') as metadata,\n", + " cast(json_extract_scalar(coupon, '$.valid') as boolean) as valid,\n", + " cast(json_extract_scalar(coupon, '$.livemode') as boolean) as livemode,\n", + " cast(json_extract_scalar(coupon, '$.amount_off') as int64) as amount_off,\n", + " cast(json_extract_scalar(coupon, '$.redeem_by') as string) as redeem_by,\n", + " cast(json_extract_scalar(coupon, '$.duration_in_months') as int64) as duration_in_months,\n", + " cast(json_extract_scalar(coupon, '$.max_redemptions') as int64) as max_redemptions,\n", + " cast(json_extract_scalar(coupon, '$.currency') as string) as currency,\n", + " cast(json_extract_scalar(coupon, '$.name') as string) as name,\n", + " cast(json_extract_scalar(coupon, '$.times_redeemed') as int64) as times_redeemed,\n", + " cast(json_extract_scalar(coupon, '$.id') as string) as id,\n", + " cast(json_extract_scalar(coupon, '$.duration') as string) as duration,\n", + " cast(json_extract_scalar(coupon, '$.object') as string) as object,\n", + " cast(json_extract_scalar(coupon, '$.percent_off') as int64) as percent_off,\n", + " cast(json_extract_scalar(coupon, '$.created') as string) as created\n", + " from customers_discount_id\n", + "),\n", + "customers_discount_coupon_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(valid as string), \"\"),\n", + " coalesce(cast(livemode as string), \"\"),\n", + " coalesce(cast(amount_off as string), \"\"),\n", + " coalesce(cast(redeem_by as string), \"\"),\n", + " coalesce(cast(duration_in_months as string), \"\"),\n", + " coalesce(cast(max_redemptions as string), \"\"),\n", + " coalesce(cast(currency as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(times_redeemed as string), \"\"),\n", + " coalesce(cast(id as string), \"\"),\n", + " coalesce(cast(duration as string), \"\"),\n", + " coalesce(cast(object as string), \"\"),\n", + " coalesce(cast(percent_off as string), \"\"),\n", + " coalesce(cast(created as string), \"\")\n", + " ))) as _customers_discount_coupon_hashid,\n", + " metadata\n", + " from customers_discount_coupon_node\n", + " \n", + ")\n", + "select \n", + " _customers_discount_coupon_hashid as _customers_discount_coupon_foreign_hashid,\n", + " metadata\n", + " from customers_discount_coupon_id\n", + "--------------------\n" + ] + } + ], + "source": [ + "print_result(generate_dbt_model(catalog=stripe, json_col=\"json_blob\", from_table=\"`airbytesandbox.data.stripe_json`\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "In File commits.sql:\n", + "--------------------\n", + "with \n", + "commits_node as (\n", + " select \n", + " \n", + " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", + " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", + " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", + " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", + " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", + " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", + " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", + " from `airbytesandbox.data.github_json`\n", + "),\n", + "commits_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(_sdc_repository as string), \"\"),\n", + " coalesce(cast(sha as string), \"\"),\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(html_url as string), \"\"),\n", + " coalesce(cast(comments_url as string), \"\"),\n", + " coalesce(cast(pr_number as string), \"\"),\n", + " coalesce(cast(pr_id as string), \"\"),\n", + " coalesce(cast(id as string), \"\")\n", + " ))) as _commits_hashid\n", + " from commits_node\n", + ")\n", + "select * from commits_id\n", + "--------------------\n", + "In File commits_parents.sql:\n", + "--------------------\n", + "with \n", + "commits_node as (\n", + " select \n", + " json_extract_array(json_blob, '$.parents') as parents,\n", + " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", + " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", + " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", + " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", + " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", + " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", + " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", + " from `airbytesandbox.data.github_json`\n", + "),\n", + "commits_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(_sdc_repository as string), \"\"),\n", + " coalesce(cast(sha as string), \"\"),\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(html_url as string), \"\"),\n", + " coalesce(cast(comments_url as string), \"\"),\n", + " coalesce(cast(pr_number as string), \"\"),\n", + " coalesce(cast(pr_id as string), \"\"),\n", + " coalesce(cast(id as string), \"\")\n", + " ))) as _commits_hashid,\n", + " parents\n", + " from commits_node\n", + " cross join unnest(parents) as parents\n", + "),\n", + "commits_parents_node as (\n", + " select \n", + " _commits_hashid as _commits_foreign_hashid,\n", + " cast(json_extract_scalar(parents, '$.sha') as string) as sha,\n", + " cast(json_extract_scalar(parents, '$.url') as string) as url,\n", + " cast(json_extract_scalar(parents, '$.html_url') as string) as html_url\n", + " from commits_id\n", + "),\n", + "commits_parents_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(sha as string), \"\"),\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(html_url as string), \"\")\n", + " ))) as _commits_parents_hashid\n", + " from commits_parents_node\n", + ")\n", + "select * from commits_parents_id\n", + "--------------------\n", + "In File commits_files.sql:\n", + "--------------------\n", + "with \n", + "commits_node as (\n", + " select \n", + " json_extract_array(json_blob, '$.files') as files,\n", + " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", + " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", + " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", + " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", + " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", + " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", + " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", + " from `airbytesandbox.data.github_json`\n", + "),\n", + "commits_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(_sdc_repository as string), \"\"),\n", + " coalesce(cast(sha as string), \"\"),\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(html_url as string), \"\"),\n", + " coalesce(cast(comments_url as string), \"\"),\n", + " coalesce(cast(pr_number as string), \"\"),\n", + " coalesce(cast(pr_id as string), \"\"),\n", + " coalesce(cast(id as string), \"\")\n", + " ))) as _commits_hashid,\n", + " files\n", + " from commits_node\n", + " cross join unnest(files) as files\n", + "),\n", + "commits_files_node as (\n", + " select \n", + " _commits_hashid as _commits_foreign_hashid,\n", + " cast(json_extract_scalar(files, '$.filename') as string) as filename,\n", + " cast(json_extract_scalar(files, '$.status') as string) as status,\n", + " cast(json_extract_scalar(files, '$.raw_url') as string) as raw_url,\n", + " cast(json_extract_scalar(files, '$.blob_url') as string) as blob_url,\n", + " cast(json_extract_scalar(files, '$.patch') as string) as patch\n", + " from commits_id\n", + "),\n", + "commits_files_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(filename as string), \"\"),\n", + " coalesce(cast(status as string), \"\"),\n", + " coalesce(cast(raw_url as string), \"\"),\n", + " coalesce(cast(blob_url as string), \"\"),\n", + " coalesce(cast(patch as string), \"\")\n", + " ))) as _commits_files_hashid\n", + " from commits_files_node\n", + ")\n", + "select * from commits_files_id\n", + "--------------------\n", + "In File commits_commit.sql:\n", + "--------------------\n", + "with \n", + "commits_node as (\n", + " select \n", + " json_extract(json_blob, '$.commit') as commit,\n", + " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", + " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", + " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", + " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", + " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", + " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", + " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", + " from `airbytesandbox.data.github_json`\n", + "),\n", + "commits_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(_sdc_repository as string), \"\"),\n", + " coalesce(cast(sha as string), \"\"),\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(html_url as string), \"\"),\n", + " coalesce(cast(comments_url as string), \"\"),\n", + " coalesce(cast(pr_number as string), \"\"),\n", + " coalesce(cast(pr_id as string), \"\"),\n", + " coalesce(cast(id as string), \"\")\n", + " ))) as _commits_hashid,\n", + " commit\n", + " from commits_node\n", + " \n", + "),\n", + "commits_commit_node as (\n", + " select \n", + " _commits_hashid as _commits_foreign_hashid,\n", + " cast(json_extract_scalar(commit, '$.url') as string) as url,\n", + " cast(json_extract_scalar(commit, '$.message') as string) as message,\n", + " cast(json_extract_scalar(commit, '$.comment_count') as int64) as comment_count\n", + " from commits_id\n", + "),\n", + "commits_commit_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(message as string), \"\"),\n", + " coalesce(cast(comment_count as string), \"\")\n", + " ))) as _commits_commit_hashid\n", + " from commits_commit_node\n", + ")\n", + "select * from commits_commit_id\n", + "--------------------\n", + "In File commits_commit_tree.sql:\n", + "--------------------\n", + "with \n", + "commits_node as (\n", + " select \n", + " json_extract(json_blob, '$.commit') as commit,\n", + " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", + " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", + " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", + " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", + " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", + " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", + " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", + " from `airbytesandbox.data.github_json`\n", + "),\n", + "commits_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(_sdc_repository as string), \"\"),\n", + " coalesce(cast(sha as string), \"\"),\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(html_url as string), \"\"),\n", + " coalesce(cast(comments_url as string), \"\"),\n", + " coalesce(cast(pr_number as string), \"\"),\n", + " coalesce(cast(pr_id as string), \"\"),\n", + " coalesce(cast(id as string), \"\")\n", + " ))) as _commits_hashid,\n", + " commit\n", + " from commits_node\n", + " \n", + "),\n", + "commits_commit_node as (\n", + " select \n", + " json_extract(commit, '$.tree') as tree,\n", + " cast(json_extract_scalar(commit, '$.url') as string) as url,\n", + " cast(json_extract_scalar(commit, '$.message') as string) as message,\n", + " cast(json_extract_scalar(commit, '$.comment_count') as int64) as comment_count\n", + " from commits_id\n", + "),\n", + "commits_commit_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(message as string), \"\"),\n", + " coalesce(cast(comment_count as string), \"\")\n", + " ))) as _commits_commit_hashid,\n", + " tree\n", + " from commits_commit_node\n", + " \n", + "),\n", + "commits_commit_tree_node as (\n", + " select \n", + " _commits_commit_hashid as _commits_commit_foreign_hashid,\n", + " cast(json_extract_scalar(tree, '$.sha') as string) as sha,\n", + " cast(json_extract_scalar(tree, '$.url') as string) as url\n", + " from commits_commit_id\n", + "),\n", + "commits_commit_tree_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(sha as string), \"\"),\n", + " coalesce(cast(url as string), \"\")\n", + " ))) as _commits_commit_tree_hashid\n", + " from commits_commit_tree_node\n", + ")\n", + "select * from commits_commit_tree_id\n", + "--------------------\n", + "In File commits_commit_author.sql:\n", + "--------------------\n", + "with \n", + "commits_node as (\n", + " select \n", + " json_extract(json_blob, '$.commit') as commit,\n", + " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", + " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", + " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", + " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", + " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", + " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", + " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", + " from `airbytesandbox.data.github_json`\n", + "),\n", + "commits_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(_sdc_repository as string), \"\"),\n", + " coalesce(cast(sha as string), \"\"),\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(html_url as string), \"\"),\n", + " coalesce(cast(comments_url as string), \"\"),\n", + " coalesce(cast(pr_number as string), \"\"),\n", + " coalesce(cast(pr_id as string), \"\"),\n", + " coalesce(cast(id as string), \"\")\n", + " ))) as _commits_hashid,\n", + " commit\n", + " from commits_node\n", + " \n", + "),\n", + "commits_commit_node as (\n", + " select \n", + " json_extract(commit, '$.author') as author,\n", + " cast(json_extract_scalar(commit, '$.url') as string) as url,\n", + " cast(json_extract_scalar(commit, '$.message') as string) as message,\n", + " cast(json_extract_scalar(commit, '$.comment_count') as int64) as comment_count\n", + " from commits_id\n", + "),\n", + "commits_commit_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(message as string), \"\"),\n", + " coalesce(cast(comment_count as string), \"\")\n", + " ))) as _commits_commit_hashid,\n", + " author\n", + " from commits_commit_node\n", + " \n", + "),\n", + "commits_commit_author_node as (\n", + " select \n", + " _commits_commit_hashid as _commits_commit_foreign_hashid,\n", + " cast(json_extract_scalar(author, '$.date') as string) as date,\n", + " cast(json_extract_scalar(author, '$.name') as string) as name,\n", + " cast(json_extract_scalar(author, '$.email') as string) as email,\n", + " cast(json_extract_scalar(author, '$.login') as string) as login\n", + " from commits_commit_id\n", + "),\n", + "commits_commit_author_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(date as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(login as string), \"\")\n", + " ))) as _commits_commit_author_hashid\n", + " from commits_commit_author_node\n", + ")\n", + "select * from commits_commit_author_id\n", + "--------------------\n", + "In File commits_commit_committer.sql:\n", + "--------------------\n", + "with \n", + "commits_node as (\n", + " select \n", + " json_extract(json_blob, '$.commit') as commit,\n", + " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", + " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", + " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", + " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", + " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", + " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", + " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", + " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", + " from `airbytesandbox.data.github_json`\n", + "),\n", + "commits_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(_sdc_repository as string), \"\"),\n", + " coalesce(cast(sha as string), \"\"),\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(html_url as string), \"\"),\n", + " coalesce(cast(comments_url as string), \"\"),\n", + " coalesce(cast(pr_number as string), \"\"),\n", + " coalesce(cast(pr_id as string), \"\"),\n", + " coalesce(cast(id as string), \"\")\n", + " ))) as _commits_hashid,\n", + " commit\n", + " from commits_node\n", + " \n", + "),\n", + "commits_commit_node as (\n", + " select \n", + " json_extract(commit, '$.committer') as committer,\n", + " cast(json_extract_scalar(commit, '$.url') as string) as url,\n", + " cast(json_extract_scalar(commit, '$.message') as string) as message,\n", + " cast(json_extract_scalar(commit, '$.comment_count') as int64) as comment_count\n", + " from commits_id\n", + "),\n", + "commits_commit_id as (\n", + " select\n", + " to_hex(md5(concat(\n", + " coalesce(cast(url as string), \"\"),\n", + " coalesce(cast(message as string), \"\"),\n", + " coalesce(cast(comment_count as string), \"\")\n", + " ))) as _commits_commit_hashid,\n", + " committer\n", + " from commits_commit_node\n", + " \n", + "),\n", + "commits_commit_committer_node as (\n", + " select \n", + " _commits_commit_hashid as _commits_commit_foreign_hashid,\n", + " cast(json_extract_scalar(committer, '$.date') as string) as date,\n", + " cast(json_extract_scalar(committer, '$.name') as string) as name,\n", + " cast(json_extract_scalar(committer, '$.email') as string) as email,\n", + " cast(json_extract_scalar(committer, '$.login') as string) as login\n", + " from commits_commit_id\n", + "),\n", + "commits_commit_committer_id as (\n", + " select\n", + " *,\n", + " to_hex(md5(concat(\n", + " coalesce(cast(date as string), \"\"),\n", + " coalesce(cast(name as string), \"\"),\n", + " coalesce(cast(email as string), \"\"),\n", + " coalesce(cast(login as string), \"\")\n", + " ))) as _commits_commit_committer_hashid\n", + " from commits_commit_committer_node\n", + ")\n", + "select * from commits_commit_committer_id\n", + "--------------------\n" + ] + } + ], + "source": [ + "print_result(generate_dbt_model(catalog=github, json_col=\"json_blob\", from_table=\"`airbytesandbox.data.github_json`\"))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.txt b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.txt new file mode 100644 index 0000000000000..fb97e07e2dffd --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.txt @@ -0,0 +1,264 @@ +""" +MIT License + +Copyright (c) 2020 Airbyte + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import json + + +# + +def load_catalog(file): + with open(file) as f: + catalog = json.load(f) + print(f"From catalog {file}:") + print("--------------------") + print(json.dumps(catalog, sort_keys=True, indent=4)) + print("--------------------") + return catalog + + +catalog = load_catalog("../sample_files/catalog.json") +github = load_catalog("../sample_files/catalog_github.json") +stripe = load_catalog("../sample_files/catalog_stripe.json") + + +# + +def is_string(property_type) -> bool: + return property_type == "string" or "string" in property_type + + +def is_integer(property_type) -> bool: + return property_type == "integer" or "integer" in property_type + + +def is_boolean(property_type) -> bool: + return property_type == "boolean" or "boolean" in property_type + + +def is_array(property_type) -> bool: + return property_type == "array" or "array" in property_type + + +def is_object(property_type) -> bool: + return property_type == "object" or "object" in property_type + + +def find_combining_schema(properties: dict): + return set(properties).intersection(set(["anyOf", "oneOf", "allOf"])) + + +def json_extract_base_property(path: str, json_col: str, name: str, definition: dict) -> str: + current = ".".join([path, name]) + if not "type" in definition: + return None + elif is_string(definition["type"]): + return f"cast(json_extract_scalar({json_col}, '{current}') as string) as {name}" + elif is_integer(definition["type"]): + return f"cast(json_extract_scalar({json_col}, '{current}') as int64) as {name}" + elif is_boolean(definition["type"]): + return f"cast(json_extract_scalar({json_col}, '{current}') as boolean) as {name}" + else: + return None + + +def json_extract_nested_property(path: str, json_col: str, name: str, definition: dict) -> str: + current = ".".join([path, name]) + if definition == None or not "type" in definition: + return (None, None) + elif is_array(definition["type"]): + return (f"json_extract_array({json_col}, '{current}') as {name}", f"cross join unnest({name}) as {name}") + elif is_object(definition["type"]): + return (f"json_extract({json_col}, '{current}') as {name}", "") + else: + return (None, None) + + +# + +def select_table(table: str, columns="*"): + return f"\nselect {columns} from {table}" + + +def extract_node_properties(path: str, json_col: str, properties: dict) -> dict: + result = {} + if properties: + for field in properties.keys(): + sql_field = json_extract_base_property(path=path, json_col=json_col, name=field, definition=properties[field]) + if sql_field: + result[field] = sql_field + return result + + +def find_properties_object(path: str, field: str, properties) -> dict: + if isinstance(properties, str) or isinstance(properties, int): + return None + else: + if "items" in properties: + return find_properties_object(path, field, properties["items"]) + elif "properties" in properties: + # we found a properties object + return {field: properties["properties"]} + elif "type" in properties and json_extract_base_property(path=path, json_col="", name="", definition=properties): + # we found a basic type + return {field: None} + elif isinstance(properties, dict): + for key in properties.keys(): + if not json_extract_base_property(path, "", key, properties[key]): + child = find_properties_object(path, key, properties[key]) + if child: + return child + elif isinstance(properties, list): + for item in properties: + child = find_properties_object(path=path, field=field, properties=item) + if child: + return child + return None + + +def extract_nested_properties(path: str, json_col: str, field: str, properties: dict) -> dict: + result = {} + if properties: + for key in properties.keys(): + combining = find_combining_schema(properties[key]) + if combining: + # skip combining schemas + for combo in combining: + found = find_properties_object(path=f"{path}.{field}.{key}", field=key, properties=properties[key][combo]) + result.update(found) + elif not "type" in properties[key]: + pass + elif is_array(properties[key]["type"]): + combining = find_combining_schema(properties[key]["items"]) + if combining: + # skip combining schemas + for combo in combining: + found = find_properties_object(path=f"{path}.{key}", field=key, properties=properties[key]["items"][combo]) + result.update(found) + else: + found = find_properties_object(path=f"{path}.{key}", field=key, properties=properties[key]["items"]) + result.update(found) + elif is_object(properties[key]["type"]): + found = find_properties_object(path=f"{path}.{key}", field=key, properties=properties[key]) + result.update(found) + return result + + +def process_node(path: str, json_col: str, name: str, properties: dict, from_table: str = "", previous="with ", inject_cols="") -> dict: + result = {} + if previous == "with ": + prefix = previous + else: + prefix = previous + "," + node_properties = extract_node_properties(path=path, json_col=json_col, properties=properties) + node_columns = ",\n ".join([sql for sql in node_properties.values()]) + # FIXME: use DBT macros to be cross_db compatible instead + hash_node_columns = ( + "coalesce(cast(" + + ' as string), ""),\n coalesce(cast('.join([column for column in node_properties.keys()]) + + ' as string), "")' + ) + node_sql = f"""{prefix} +{name}_node as ( + select + {inject_cols} + {node_columns} + from {from_table} +), +{name}_with_id as ( + select + *, + to_hex(md5(concat( + {hash_node_columns} + ))) as _{name}_hashid + from {name}_node +)""" + # SQL Query for current node's basic properties + result[name] = node_sql + select_table(f"{name}_with_id") + + children_columns = extract_nested_properties(path=path, json_col=json_col, field=name, properties=properties) + if children_columns: + for col in children_columns.keys(): + child_col, join_child_table = json_extract_nested_property(path=path, json_col=json_col, name=col, definition=properties[col]) + child_sql = f"""{prefix} +{name}_node as ( + select + {child_col}, + {node_columns} + from {from_table} +), +{name}_with_id as ( + select + to_hex(md5(concat( + {hash_node_columns} + ))) as _{name}_hashid, + {col} + from {name}_node + {join_child_table} +)""" + if children_columns[col]: + children = process_node( + path="$", + json_col=col, + name=f"{name}_{col}", + properties=children_columns[col], + from_table=f"{name}_with_id", + previous=child_sql, + inject_cols=f"_{name}_hashid as _{name}_foreign_hashid,", + ) + result.update(children) + else: + # SQL Query for current node's basic properties + result[f"{name}_{col}"] = child_sql + select_table( + f"{name}_with_id", + columns=f""" + _{name}_hashid as _{name}_foreign_hashid, + {col} +""", + ) + return result + + +def generate_dbt_model(catalog: dict, json_col: str, from_table: str) -> dict: + result = {} + for obj in catalog["streams"]: + name = obj["name"] + if "json_schema" in obj: + properties = obj["json_schema"]["properties"] + elif "schema" in obj: + properties = obj["schema"]["properties"] + result.update(process_node(path="$", json_col=json_col, name=name, properties=properties, from_table=from_table)) + return result + + +def print_result(result): + for name in result.keys(): + print(f"In File {name}.sql:") + print("--------------------") + print(result[name]) + print("--------------------") + + +print_result(generate_dbt_model(catalog=catalog, json_col="json_blob", from_table="`airbytesandbox.data.one_recipe_json`")) +# - + +print_result(generate_dbt_model(catalog=stripe, json_col="json_blob", from_table="`airbytesandbox.data.stripe_json`")) + +print_result(generate_dbt_model(catalog=github, json_col="json_blob", from_table="`airbytesandbox.data.github_json`")) diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog.json new file mode 100644 index 0000000000000..7cb3dd59836db --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog.json @@ -0,0 +1,514 @@ +{ + "streams": [ + { + "name": "one_recipe", + "json_schema": + { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "The root schema", + "description": "The root schema comprises the entire JSON document.", + "default": {}, + "examples": [ + { + "Name": "Christmas pie", + "url": "https://www.bbcgoodfood.com/recipes/2793/christmas-pie", + "Description": "Combine a few key Christmas flavours here to make a pie that both children and adults will adore", + "Author": "Mary Cadogan", + "Ingredients": [ + { + "quantity": "2 tbsp", + "ingredient": "olive oil", + "nutritionfacts": { + "calories": 119, + "fat": "13.5g" + } + }, + { + "quantity": "knob", + "ingredient": "butter", + "nutritionfacts": { + "calories": 102, + "fat": "11.5g" + } + }, + { + "quantity": "1", + "ingredient": "onion", + "preparation": "finely chopped", + "nutritionfacts": { + "calories": 102, + "fat": "11.5g" + } + }, + { + "quantity": "500g", + "ingredient": "sausagemeat or skinned sausages", + "nutritionfacts": { + "calories": 268, + "fat": "18g" + } + }, + { + "quantity": "1", + "ingredient": "lemon", + "preparation": "grated zest", + "nutritionfacts": { + "calories": 13 + } + }, + { + "quantity": "100g", + "ingredient": "fresh white breadcrumbs" + }, + { + "quantity": "85g", + "ingredient": "ready-to-eat dried apricots", + "preparation": "chopped", + "nutritionfacts": { + "calories": 34, + "fat": "0.27g" + } + }, + { + "quantity": "58g", + "ingredient": "chestnut, canned or vacuum-packed", + "preparation": "chopped", + "nutritionfacts": { + "calories": 77, + "fat": "1g" + } + }, + { + "quantity": "2 tsp", + "ingredient": "fresh or dried thyme", + "preparation": "chopped" + }, + { + "quantity": "100g", + "ingredient": "cranberries, fresh or frozen" + }, + { + "quantity": "500g", + "ingredient": "boneless, skinless chicken breasts", + "nutritionfacts": { + "calories": 284, + "fat": "6.2g" + } + }, + { + "quantity": "500g", + "ingredient": "pack ready-made shortcrust pastry" + }, + { + "quantity": "1", + "ingredient": "beaten egg", + "preparation": "to glaze", + "nutritionfacts": { + "calories": 75, + "fat": "5g" + } + } + ], + "Method": [ + "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", + "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.", + "Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.", + "Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.", + "Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles." + ] + } + ], + "required": [ + "Name", + "url", + "Description", + "Author", + "Ingredients", + "Method" + ], + "properties": { + "Name": { + "$id": "#/properties/Name", + "type": "string", + "title": "The Name schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "Christmas pie" + ] + }, + "url": { + "$id": "#/properties/url", + "type": "string", + "title": "The url schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "https://www.bbcgoodfood.com/recipes/2793/christmas-pie" + ] + }, + "Description": { + "$id": "#/properties/Description", + "type": "string", + "title": "The Description schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "Combine a few key Christmas flavours here to make a pie that both children and adults will adore" + ] + }, + "Author": { + "$id": "#/properties/Author", + "type": "string", + "title": "The Author schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "Mary Cadogan" + ] + }, + "Ingredients": { + "$id": "#/properties/Ingredients", + "type": "array", + "title": "The Ingredients schema", + "description": "An explanation about the purpose of this instance.", + "default": [], + "examples": [ + [ + { + "quantity": "2 tbsp", + "ingredient": "olive oil", + "nutritionfacts": { + "calories": 119, + "fat": "13.5g" + } + }, + { + "quantity": "knob", + "ingredient": "butter", + "nutritionfacts": { + "calories": 102, + "fat": "11.5g" + } + } + ] + ], + "additionalItems": true, + "items": { + "$id": "#/properties/Ingredients/items", + "anyOf": [ + { + "$id": "#/properties/Ingredients/items/anyOf/0", + "type": "object", + "title": "The first anyOf schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "quantity": "2 tbsp", + "ingredient": "olive oil", + "nutritionfacts": { + "calories": 119, + "fat": "13.5g" + } + } + ], + "required": [ + "quantity", + "ingredient", + "nutritionfacts" + ], + "properties": { + "quantity": { + "$id": "#/properties/Ingredients/items/anyOf/0/properties/quantity", + "type": "string", + "title": "The quantity schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "2 tbsp" + ] + }, + "ingredient": { + "$id": "#/properties/Ingredients/items/anyOf/0/properties/ingredient", + "type": "string", + "title": "The ingredient schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "olive oil" + ] + }, + "nutritionfacts": { + "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts", + "type": "object", + "title": "The nutritionfacts schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "calories": 119, + "fat": "13.5g" + } + ], + "required": [ + "calories", + "fat" + ], + "properties": { + "calories": { + "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/calories", + "type": "integer", + "title": "The calories schema", + "description": "An explanation about the purpose of this instance.", + "default": 0, + "examples": [ + 119 + ] + }, + "fat": { + "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/fat", + "type": "string", + "title": "The fat schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "13.5g" + ] + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + { + "$id": "#/properties/Ingredients/items/anyOf/1", + "type": "object", + "title": "The second anyOf schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "quantity": "1", + "ingredient": "onion", + "preparation": "finely chopped", + "nutritionfacts": { + "calories": 102, + "fat": "11.5g" + } + } + ], + "required": [ + "quantity", + "ingredient", + "preparation", + "nutritionfacts" + ], + "properties": { + "quantity": { + "$id": "#/properties/Ingredients/items/anyOf/1/properties/quantity", + "type": "string", + "title": "The quantity schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "1" + ] + }, + "ingredient": { + "$id": "#/properties/Ingredients/items/anyOf/1/properties/ingredient", + "type": "string", + "title": "The ingredient schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "onion" + ] + }, + "preparation": { + "$id": "#/properties/Ingredients/items/anyOf/1/properties/preparation", + "type": "string", + "title": "The preparation schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "finely chopped" + ] + }, + "nutritionfacts": { + "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts", + "type": "object", + "title": "The nutritionfacts schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "calories": 102, + "fat": "11.5g" + } + ], + "required": [ + "calories", + "fat" + ], + "properties": { + "calories": { + "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/calories", + "type": "integer", + "title": "The calories schema", + "description": "An explanation about the purpose of this instance.", + "default": 0, + "examples": [ + 102 + ] + }, + "fat": { + "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/fat", + "type": "string", + "title": "The fat schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "11.5g" + ] + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + { + "$id": "#/properties/Ingredients/items/anyOf/2", + "type": "object", + "title": "The third anyOf schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "quantity": "100g", + "ingredient": "fresh white breadcrumbs" + } + ], + "required": [ + "quantity", + "ingredient" + ], + "properties": { + "quantity": { + "$id": "#/properties/Ingredients/items/anyOf/2/properties/quantity", + "type": "string", + "title": "The quantity schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "100g" + ] + }, + "ingredient": { + "$id": "#/properties/Ingredients/items/anyOf/2/properties/ingredient", + "type": "string", + "title": "The ingredient schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "fresh white breadcrumbs" + ] + } + }, + "additionalProperties": true + }, + { + "$id": "#/properties/Ingredients/items/anyOf/3", + "type": "object", + "title": "The fourth anyOf schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "quantity": "2 tsp", + "ingredient": "fresh or dried thyme", + "preparation": "chopped" + } + ], + "required": [ + "quantity", + "ingredient", + "preparation" + ], + "properties": { + "quantity": { + "$id": "#/properties/Ingredients/items/anyOf/3/properties/quantity", + "type": "string", + "title": "The quantity schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "2 tsp" + ] + }, + "ingredient": { + "$id": "#/properties/Ingredients/items/anyOf/3/properties/ingredient", + "type": "string", + "title": "The ingredient schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "fresh or dried thyme" + ] + }, + "preparation": { + "$id": "#/properties/Ingredients/items/anyOf/3/properties/preparation", + "type": "string", + "title": "The preparation schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "chopped" + ] + } + }, + "additionalProperties": true + } + ] + } + }, + "Method": { + "$id": "#/properties/Method", + "type": "array", + "title": "The Method schema", + "description": "An explanation about the purpose of this instance.", + "default": [], + "examples": [ + [ + "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", + "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins." + ] + ], + "additionalItems": true, + "items": { + "$id": "#/properties/Method/items", + "anyOf": [ + { + "$id": "#/properties/Method/items/anyOf/0", + "type": "string", + "title": "The first anyOf schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", + "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins." + ] + } + ] + } + } + }, + "additionalProperties": true + } + } + ] +} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_exchangerateapi.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_exchangerateapi.json new file mode 100644 index 0000000000000..36f42a5a028b4 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_exchangerateapi.json @@ -0,0 +1,46 @@ +{ + "streams": [ + { + "stream": "exchange_rate", + "schema": { + "type": "object", + "properties": { + "date": { "type": "string", "format": "date-time" }, + "CAD": { "type": ["null", "number"] }, + "HKD": { "type": ["null", "number"] }, + "ISK": { "type": ["null", "number"] }, + "PHP": { "type": ["null", "number"] }, + "DKK": { "type": ["null", "number"] }, + "HUF": { "type": ["null", "number"] }, + "CZK": { "type": ["null", "number"] }, + "GBP": { "type": ["null", "number"] }, + "RON": { "type": ["null", "number"] }, + "SEK": { "type": ["null", "number"] }, + "IDR": { "type": ["null", "number"] }, + "INR": { "type": ["null", "number"] }, + "BRL": { "type": ["null", "number"] }, + "RUB": { "type": ["null", "number"] }, + "HRK": { "type": ["null", "number"] }, + "JPY": { "type": ["null", "number"] }, + "THB": { "type": ["null", "number"] }, + "CHF": { "type": ["null", "number"] }, + "EUR": { "type": ["null", "number"] }, + "MYR": { "type": ["null", "number"] }, + "BGN": { "type": ["null", "number"] }, + "TRY": { "type": ["null", "number"] }, + "CNY": { "type": ["null", "number"] }, + "NOK": { "type": ["null", "number"] }, + "NZD": { "type": ["null", "number"] }, + "ZAR": { "type": ["null", "number"] }, + "USD": { "type": ["null", "number"] }, + "MXN": { "type": ["null", "number"] }, + "SGD": { "type": ["null", "number"] }, + "AUD": { "type": ["null", "number"] }, + "ILS": { "type": ["null", "number"] }, + "KRW": { "type": ["null", "number"] }, + "PLN": { "type": ["null", "number"] } + } + } + } + ] +} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_file.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_file.json new file mode 100644 index 0000000000000..9baccf44184c8 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_file.json @@ -0,0 +1,34 @@ +{ + "streams": [ + { + "name": "my_own_data_sample/my_file.csv", + "json_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "date": { + "type": "string" + }, + "key": { + "type": "string" + }, + "total_confirmed": { + "type": "number" + }, + "total_healed": { + "type": "number" + }, + "total_deceased": { + "type": "number" + }, + "total_recovered": { + "type": "number" + }, + "total_tested": { + "type": "number" + } + } + } + } + ] +} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_github.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_github.json new file mode 100644 index 0000000000000..9b38d3536c2e3 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_github.json @@ -0,0 +1,169 @@ +{ + "streams": [ + { + "name": "commits", + "json_schema": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "_sdc_repository": { + "type": ["string"] + }, + "sha": { + "type": ["null", "string"], + "description": "The git commit hash" + }, + "url": { + "type": ["null", "string"] + }, + "parents": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "sha": { + "type": ["null", "string"], + "description": "The git hash of the parent commit" + }, + "url": { + "type": ["null", "string"], + "description": "The URL to the parent commit" + }, + "html_url": { + "type": ["null", "string"], + "description": "The HTML URL to the parent commit" + } + } + } + }, + "files": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "properties": { + "filename": { + "type": ["null", "string"] + }, + "additions": { + "type": ["null", "number"] + }, + "deletions": { + "type": ["null", "number"] + }, + "changes": { + "type": ["null", "number"] + }, + "status": { + "type": ["null", "string"] + }, + "raw_url": { + "type": ["null", "string"] + }, + "blob_url": { + "type": ["null", "string"] + }, + "patch": { + "type": ["null", "string"] + } + } + } + }, + "html_url": { + "type": ["null", "string"], + "description": "The HTML URL to the commit" + }, + "comments_url": { + "type": ["null", "string"], + "description": "The URL to the commit's comments page" + }, + "commit": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "url": { + "type": ["null", "string"], + "description": "The URL to the commit" + }, + "tree": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "sha": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + } + }, + "author": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "date": { + "type": ["null", "string"], + "format": "date-time", + "description": "The date the author committed the change" + }, + "name": { + "type": ["null", "string"], + "description": "The author's name" + }, + "email": { + "type": ["null", "string"], + "description": "The author's email" + }, + "login": { + "type": ["null", "string"], + "description": "The author's login" + } + } + }, + "message": { + "type": ["null", "string"], + "description": "The commit message" + }, + "committer": { + "type": ["null", "object"], + "additionalProperties": false, + "properties": { + "date": { + "type": ["null", "string"], + "format": "date-time", + "description": "The date the committer committed the change" + }, + "name": { + "type": ["null", "string"], + "description": "The committer's name" + }, + "email": { + "type": ["null", "string"], + "description": "The committer's email" + }, + "login": { + "type": ["null", "string"], + "description": "The committer's login" + } + } + }, + "comment_count": { + "type": ["null", "integer"], + "description": "The number of comments on the commit" + } + } + }, + "pr_number": { + "type": ["null", "integer"] + }, + "pr_id": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + } + } + } + } + ] +} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_google-sheets.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_google-sheets.json new file mode 100644 index 0000000000000..438f0624af9e5 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_google-sheets.json @@ -0,0 +1,46 @@ +{ + "streams": [ + { + "name": "sheet1", + "json_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "header1": { + "type": "string" + }, + "irrelevant": { + "type": "string" + }, + "header3": { + "type": "string" + }, + "ignored": { + "type": "string" + } + } + } + }, + { + "name": "sheet2", + "json_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "header1": { + "type": "string" + }, + "irrelevant": { + "type": "string" + }, + "header3": { + "type": "string" + }, + "ignored": { + "type": "string" + } + } + } + } + ] +} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_hubspot.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_hubspot.json new file mode 100644 index 0000000000000..f1e79f8e4bded --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_hubspot.json @@ -0,0 +1,79 @@ +{ + "streams": [ + { + "name": "owners", + "json_schema": { + "type": "object", + "properties": { + "portalId": { + "type": ["null", "integer"] + }, + "ownerId": { + "type": ["null", "integer"] + }, + "type": { + "type": ["null", "string"] + }, + "firstName": { + "type": ["null", "string"] + }, + "lastName": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + }, + "createdAt": { + "type": ["null", "string"], + "format": "date-time" + }, + "signature": { + "type": ["null", "string"] + }, + "updatedAt": { + "type": ["null", "string"], + "format": "date-time" + }, + "hasContactsAccess": { + "type": ["null", "boolean"] + }, + "isActive": { + "type": ["null", "boolean"] + }, + "activeUserId": { + "type": ["null", "integer"] + }, + "userIdIncludingInactive": { + "type": ["null", "integer"] + }, + "remoteList": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": ["null", "integer"] + }, + "portalId": { + "type": ["null", "integer"] + }, + "ownerId": { + "type": ["null", "integer"] + }, + "remoteId": { + "type": ["null", "string"] + }, + "remoteType": { + "type": ["null", "string"] + }, + "active": { + "type": ["null", "boolean"] + } + } + } + } + } + } + } + ] +} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_salesforce.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_salesforce.json new file mode 100644 index 0000000000000..85d1bca893957 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_salesforce.json @@ -0,0 +1,22 @@ +{ + "streams": [ + { + "name": "User", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "Id": { + "type": "string" + }, + "Username": { + "type": ["null", "string"] + }, + "Name": { + "type": ["null", "string"] + } + } + } + } + ] +} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_stripe.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_stripe.json new file mode 100644 index 0000000000000..de802f60139cd --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_stripe.json @@ -0,0 +1,863 @@ +{ + "streams": [ + { + "name": "customers", + "schema": { + "type": ["null", "object"], + "properties": { + "metadata": { + "type": ["null", "object"], + "properties": {} + }, + "shipping": { + "type": ["null", "object"], + "properties": { + "address": { + "type": ["null", "object"], + "properties": { + "line2": { + "type": ["null", "string"] + }, + "state": { + "type": ["null", "string"] + }, + "city": { + "type": ["null", "string"] + }, + "postal_code": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "line1": { + "type": ["null", "string"] + } + } + }, + "name": { + "type": ["null", "string"] + }, + "phone": { + "type": ["null", "string"] + } + } + }, + "sources": { + "anyOf": [ + { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "properties": { + "metadata": { + "type": ["null", "object"], + "properties": {} + }, + "type": { + "type": ["null", "string"] + }, + "address_zip": { + "type": ["null", "string"] + }, + "livemode": { + "type": ["null", "boolean"] + }, + "card": { + "type": ["null", "object"], + "properties": { + "fingerprint": { + "type": ["null", "string"] + }, + "last4": { + "type": ["null", "string"] + }, + "dynamic_last4": { + "type": ["null", "string"] + }, + "address_line1_check": { + "type": ["null", "string"] + }, + "exp_month": { + "type": ["null", "integer"] + }, + "tokenization_method": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "exp_year": { + "type": ["null", "integer"] + }, + "three_d_secure": { + "type": ["null", "string"] + }, + "funding": { + "type": ["null", "string"] + }, + "brand": { + "type": ["null", "string"] + }, + "cvc_check": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "address_zip_check": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } + }, + "statement_descriptor": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "address_country": { + "type": ["null", "string"] + }, + "funding": { + "type": ["null", "string"] + }, + "dynamic_last4": { + "type": ["null", "string"] + }, + "exp_year": { + "type": ["null", "integer"] + }, + "last4": { + "type": ["null", "string"] + }, + "exp_month": { + "type": ["null", "integer"] + }, + "brand": { + "type": ["null", "string"] + }, + "address_line2": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "object": { + "type": ["null", "string"] + }, + "amount": { + "type": ["null", "integer"] + }, + "cvc_check": { + "type": ["null", "string"] + }, + "usage": { + "type": ["null", "string"] + }, + "address_line1": { + "type": ["null", "string"] + }, + "owner": { + "type": ["null", "object"], + "properties": { + "verified_address": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + }, + "address": { + "type": ["null", "object"], + "properties": { + "line2": { + "type": ["null", "string"] + }, + "state": { + "type": ["null", "string"] + }, + "city": { + "type": ["null", "string"] + }, + "postal_code": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "line1": { + "type": ["null", "string"] + } + } + }, + "verified_email": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "phone": { + "type": ["null", "string"] + }, + "verified_name": { + "type": ["null", "string"] + }, + "verified_phone": { + "type": ["null", "string"] + } + } + }, + "tokenization_method": { + "type": ["null", "string"] + }, + "client_secret": { + "type": ["null", "string"] + }, + "fingerprint": { + "type": ["null", "string"] + }, + "address_city": { + "type": ["null", "string"] + }, + "currency": { + "type": ["null", "string"] + }, + "address_line1_check": { + "type": ["null", "string"] + }, + "receiver": { + "type": ["null", "object"], + "properties": { + "refund_attributes_method": { + "type": ["null", "string"] + }, + "amount_returned": { + "type": ["null", "integer"] + }, + "amount_received": { + "type": ["null", "integer"] + }, + "refund_attributes_status": { + "type": ["null", "string"] + }, + "address": { + "type": ["null", "string"] + }, + "amount_charged": { + "type": ["null", "integer"] + } + } + }, + "flow": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "ach_credit_transfer": { + "type": ["null", "object"], + "properties": { + "bank_name": { + "type": ["null", "string"] + }, + "fingerprint": { + "type": ["null", "string"] + }, + "routing_number": { + "type": ["null", "string"] + }, + "swift_code": { + "type": ["null", "string"] + }, + "refund_account_holder_type": { + "type": ["null", "string"] + }, + "refund_account_holder_name": { + "type": ["null", "string"] + }, + "refund_account_number": { + "type": ["null", "string"] + }, + "refund_routing_number": { + "type": ["null", "string"] + }, + "account_number": { + "type": ["null", "string"] + } + } + }, + "customer": { + "type": ["null", "string"] + }, + "address_zip_check": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "created": { + "type": ["null", "string"], + "format": "date-time" + }, + "address_state": { + "type": ["null", "string"] + }, + "alipay": { + "type": ["null", "object"], + "properties": {} + }, + "bancontact": { + "type": ["null", "object"], + "properties": {} + }, + "eps": { + "type": ["null", "object"], + "properties": {} + }, + "ideal": { + "type": ["null", "object"], + "properties": {} + }, + "multibanco": { + "type": ["null", "object"], + "properties": {} + }, + "redirect": { + "type": ["null", "object"], + "properties": { + "failure_reason": { + "type": ["null", "string"] + }, + "return_url": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + } + } + } + } + }, + { + "type": ["null", "object"], + "properties": { + "metadata": { + "type": ["null", "object"], + "properties": {} + }, + "type": { + "type": ["null", "string"] + }, + "address_zip": { + "type": ["null", "string"] + }, + "livemode": { + "type": ["null", "boolean"] + }, + "card": { + "type": ["null", "object"], + "properties": { + "fingerprint": { + "type": ["null", "string"] + }, + "last4": { + "type": ["null", "string"] + }, + "dynamic_last4": { + "type": ["null", "string"] + }, + "address_line1_check": { + "type": ["null", "string"] + }, + "exp_month": { + "type": ["null", "integer"] + }, + "tokenization_method": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "exp_year": { + "type": ["null", "integer"] + }, + "three_d_secure": { + "type": ["null", "string"] + }, + "funding": { + "type": ["null", "string"] + }, + "brand": { + "type": ["null", "string"] + }, + "cvc_check": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "address_zip_check": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } + }, + "statement_descriptor": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "address_country": { + "type": ["null", "string"] + }, + "funding": { + "type": ["null", "string"] + }, + "dynamic_last4": { + "type": ["null", "string"] + }, + "exp_year": { + "type": ["null", "integer"] + }, + "last4": { + "type": ["null", "string"] + }, + "exp_month": { + "type": ["null", "integer"] + }, + "brand": { + "type": ["null", "string"] + }, + "address_line2": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "object": { + "type": ["null", "string"] + }, + "amount": { + "type": ["null", "integer"] + }, + "cvc_check": { + "type": ["null", "string"] + }, + "usage": { + "type": ["null", "string"] + }, + "address_line1": { + "type": ["null", "string"] + }, + "owner": { + "type": ["null", "object"], + "properties": { + "verified_address": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + }, + "address": { + "type": ["null", "object"], + "properties": { + "line2": { + "type": ["null", "string"] + }, + "state": { + "type": ["null", "string"] + }, + "city": { + "type": ["null", "string"] + }, + "postal_code": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "line1": { + "type": ["null", "string"] + } + } + }, + "verified_email": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "phone": { + "type": ["null", "string"] + }, + "verified_name": { + "type": ["null", "string"] + }, + "verified_phone": { + "type": ["null", "string"] + } + } + }, + "tokenization_method": { + "type": ["null", "string"] + }, + "client_secret": { + "type": ["null", "string"] + }, + "fingerprint": { + "type": ["null", "string"] + }, + "address_city": { + "type": ["null", "string"] + }, + "currency": { + "type": ["null", "string"] + }, + "address_line1_check": { + "type": ["null", "string"] + }, + "receiver": { + "type": ["null", "object"], + "properties": { + "refund_attributes_method": { + "type": ["null", "string"] + }, + "amount_returned": { + "type": ["null", "integer"] + }, + "amount_received": { + "type": ["null", "integer"] + }, + "refund_attributes_status": { + "type": ["null", "string"] + }, + "address": { + "type": ["null", "string"] + }, + "amount_charged": { + "type": ["null", "integer"] + } + } + }, + "flow": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "ach_credit_transfer": { + "type": ["null", "object"], + "properties": { + "bank_name": { + "type": ["null", "string"] + }, + "fingerprint": { + "type": ["null", "string"] + }, + "routing_number": { + "type": ["null", "string"] + }, + "swift_code": { + "type": ["null", "string"] + }, + "refund_account_holder_type": { + "type": ["null", "string"] + }, + "refund_account_holder_name": { + "type": ["null", "string"] + }, + "refund_account_number": { + "type": ["null", "string"] + }, + "refund_routing_number": { + "type": ["null", "string"] + }, + "account_number": { + "type": ["null", "string"] + } + } + }, + "customer": { + "type": ["null", "string"] + }, + "address_zip_check": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "created": { + "type": ["null", "string"], + "format": "date-time" + }, + "address_state": { + "type": ["null", "string"] + }, + "alipay": { + "type": ["null", "object"], + "properties": {} + }, + "bancontact": { + "type": ["null", "object"], + "properties": {} + }, + "eps": { + "type": ["null", "object"], + "properties": {} + }, + "ideal": { + "type": ["null", "object"], + "properties": {} + }, + "multibanco": { + "type": ["null", "object"], + "properties": {} + }, + "redirect": { + "type": ["null", "object"], + "properties": { + "failure_reason": { + "type": ["null", "string"] + }, + "return_url": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + } + } + } + } + } + ] + }, + "delinquent": { + "type": ["null", "boolean"] + }, + "description": { + "type": ["null", "string"] + }, + "livemode": { + "type": ["null", "boolean"] + }, + "default_source": { + "type": ["null", "string"] + }, + "cards": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "properties": { + "metadata": { + "type": ["null", "object"], + "properties": {} + }, + "object": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "exp_month": { + "type": ["null", "integer"] + }, + "dynamic_last4": { + "type": ["null", "string"] + }, + "exp_year": { + "type": ["null", "integer"] + }, + "last4": { + "type": ["null", "string"] + }, + "funding": { + "type": ["null", "string"] + }, + "brand": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "customer": { + "type": ["null", "string"] + }, + "cvc_check": { + "type": ["null", "string"] + }, + "address_line2": { + "type": ["null", "string"] + }, + "address_line1": { + "type": ["null", "string"] + }, + "fingerprint": { + "type": ["null", "string"] + }, + "address_zip": { + "type": ["null", "string"] + }, + "address_city": { + "type": ["null", "string"] + }, + "address_country": { + "type": ["null", "string"] + }, + "address_line1_check": { + "type": ["null", "string"] + }, + "tokenization_method": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "address_state": { + "type": ["null", "string"] + }, + "address_zip_check": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + } + } + } + }, + "email": { + "type": ["null", "string"] + }, + "default_card": { + "type": ["null", "string"] + }, + "subscriptions": { + "type": ["null", "array"], + "items": { + "type": ["null", "string"] + } + }, + "discount": { + "type": ["null", "object"], + "properties": { + "end": { + "type": ["null", "string"], + "format": "date-time" + }, + "coupon": { + "type": ["null", "object"], + "properties": { + "metadata": { + "type": ["null", "object"], + "properties": {} + }, + "valid": { + "type": ["null", "boolean"] + }, + "livemode": { + "type": ["null", "boolean"] + }, + "amount_off": { + "type": ["null", "integer"] + }, + "redeem_by": { + "type": ["null", "string"], + "format": "date-time" + }, + "duration_in_months": { + "type": ["null", "integer"] + }, + "percent_off_precise": { + "type": ["null", "number"] + }, + "max_redemptions": { + "type": ["null", "integer"] + }, + "currency": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "times_redeemed": { + "type": ["null", "integer"] + }, + "id": { + "type": ["null", "string"] + }, + "duration": { + "type": ["null", "string"] + }, + "object": { + "type": ["null", "string"] + }, + "percent_off": { + "type": ["null", "integer"] + }, + "created": { + "type": ["null", "string"], + "format": "date-time" + } + } + }, + "customer": { + "type": ["null", "string"] + }, + "start": { + "type": ["null", "string"], + "format": "date-time" + }, + "object": { + "type": ["null", "string"] + }, + "subscription": { + "type": ["null", "string"] + } + } + }, + "account_balance": { + "type": ["null", "integer"] + }, + "currency": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "string"] + }, + "invoice_prefix": { + "type": ["null", "string"] + }, + "tax_info_verification": { + "type": ["null", "string"] + }, + "object": { + "type": ["null", "string"] + }, + "created": { + "type": ["null", "string"], + "format": "date-time" + }, + "tax_info": { + "type": ["null", "string"] + }, + "updated": { + "type": ["null", "string"], + "format": "date-time" + } + } + } + } + ] +} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_line_recipe.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_line_recipe.json new file mode 100644 index 0000000000000..46be0837acd14 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_line_recipe.json @@ -0,0 +1 @@ +{ "Name": "Christmas pie", "url": "https://www.bbcgoodfood.com/recipes/2793/christmas-pie", "Description": "Combine a few key Christmas flavours here to make a pie that both children and adults will adore", "Author": "Mary Cadogan", "Ingredients": [ { "quantity": "2 tbsp", "ingredient": "olive oil", "nutritionfacts": { "calories": 119, "fat": "13.5g" } }, { "quantity": "knob", "ingredient": "butter", "nutritionfacts": { "calories": 102, "fat": "11.5g" } }, { "quantity": "1", "ingredient": "onion", "preparation": "finely chopped", "nutritionfacts": { "calories": 102, "fat": "11.5g" } }, { "quantity": "500g", "ingredient": "sausagemeat or skinned sausages", "nutritionfacts": { "calories": 268, "fat": "18g" } }, { "quantity": "1", "ingredient": "lemon", "preparation": "grated zest", "nutritionfacts": { "calories": 13 } }, { "quantity": "100g", "ingredient": "fresh white breadcrumbs" }, { "quantity": "85g", "ingredient": "ready-to-eat dried apricots", "preparation": "chopped", "nutritionfacts": { "calories": 34, "fat": "0.27g" } }, { "quantity": "58g", "ingredient": "chestnut, canned or vacuum-packed", "preparation": "chopped", "nutritionfacts": { "calories": 77, "fat": "1g" } }, { "quantity": "2 tsp", "ingredient": "fresh or dried thyme", "preparation": "chopped" }, { "quantity": "100g", "ingredient": "cranberries, fresh or frozen" }, { "quantity": "500g", "ingredient": "boneless, skinless chicken breasts", "nutritionfacts": { "calories": 284, "fat": "6.2g" } }, { "quantity": "500g", "ingredient": "pack ready-made shortcrust pastry" }, { "quantity": "1", "ingredient": "beaten egg", "preparation": "to glaze", "nutritionfacts": { "calories": 75, "fat": "5g" } } ], "Method": [ "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.", "Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.", "Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.", "Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles." ]} \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_recipe.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_recipe.json new file mode 100644 index 0000000000000..d70379ae858bd --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_recipe.json @@ -0,0 +1,108 @@ +{ + "Name": "Christmas pie", + "url": "https://www.bbcgoodfood.com/recipes/2793/christmas-pie", + "Description": "Combine a few key Christmas flavours here to make a pie that both children and adults will adore", + "Author": "Mary Cadogan", + "Ingredients": [ + { + "quantity": "2 tbsp", + "ingredient": "olive oil", + "nutritionfacts": { + "calories": 119, + "fat": "13.5g" + } + }, + { + "quantity": "knob", + "ingredient": "butter", + "nutritionfacts": { + "calories": 102, + "fat": "11.5g" + } + }, + { + "quantity": "1", + "ingredient": "onion", + "preparation": "finely chopped", + "nutritionfacts": { + "calories": 102, + "fat": "11.5g" + } + }, + { + "quantity": "500g", + "ingredient": "sausagemeat or skinned sausages", + "nutritionfacts": { + "calories": 268, + "fat": "18g" + } + }, + { + "quantity": "1", + "ingredient": "lemon", + "preparation": "grated zest", + "nutritionfacts": { + "calories": 13 + } + }, + { + "quantity": "100g", + "ingredient": "fresh white breadcrumbs" + }, + { + "quantity": "85g", + "ingredient": "ready-to-eat dried apricots", + "preparation": "chopped", + "nutritionfacts": { + "calories": 34, + "fat": "0.27g" + } + }, + { + "quantity": "58g", + "ingredient": "chestnut, canned or vacuum-packed", + "preparation": "chopped", + "nutritionfacts": { + "calories": 77, + "fat": "1g" + } + }, + { + "quantity": "2 tsp", + "ingredient": "fresh or dried thyme", + "preparation": "chopped" + }, + { + "quantity": "100g", + "ingredient": "cranberries, fresh or frozen" + }, + { + "quantity": "500g", + "ingredient": "boneless, skinless chicken breasts", + "nutritionfacts": { + "calories": 284, + "fat": "6.2g" + } + }, + { + "quantity": "500g", + "ingredient": "pack ready-made shortcrust pastry" + }, + { + "quantity": "1", + "ingredient": "beaten egg", + "preparation": "to glaze", + "nutritionfacts": { + "calories": 75, + "fat": "5g" + } + } + ], + "Method": [ + "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", + "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.", + "Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.", + "Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.", + "Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles." + ] +} \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/profiles.yml b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/profiles.yml new file mode 100755 index 0000000000000..0504166176eaf --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/profiles.yml @@ -0,0 +1,40 @@ + +# This configuration file specifies information about connections to +# your data warehouse(s). The file contains a series of "profiles." +# Profiles specify database credentials and connection information +# + +# Top-level configs that apply to all profiles are set here +config: + partial_parse: true + printer_width: 120 + send_anonymous_usage_stats: False + use_colors: True + +# see https://docs.getdbt.com/docs/supported-databases for more details +# +# Commonly, it's helpful to define multiple targets for a profile. For example, +# these targets might be `dev` and `prod`. Whereas the `dev` target points to +# a development schema (eg. dbt_dev), the `prod` schema should point to the +# prod schema (eg. analytics). Analytical/BI tools should point to the +# prod schema so that local development does not interfere with analysis. +# + +dev: + target: dev # default target is dev unless changed at run time + outputs: + dev: + type: bigquery + method: service-account + project: airbytesandbox + schema: dbt_chris + threads: 32 + timeout_seconds: 300 + location: europe-west1 + priority: interactive + keyfile: /home/user/Workspace/secrets/gcs.json + retries: 2 + # If a query would bill more than a gigabyte of data, then + # BigQuery will reject the query + # maximum_bytes_billed: 1000000000 + maximum_bytes_billed: 10000000 diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/recipes.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/recipes.json new file mode 100644 index 0000000000000..655ec2518c025 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/recipes.json @@ -0,0 +1,10 @@ +{"Name": "Christmas pie", "url": "https://www.bbcgoodfood.com/recipes/2793/christmas-pie", "Description": "Combine a few key Christmas flavours here to make a pie that both children and adults will adore", "Author": "Mary Cadogan", "Ingredients": ["2 tbsp olive oil", "knob butter", "1 onion, finely chopped", "500g sausagemeat or skinned sausages", "grated zest of 1 lemon", "100g fresh white breadcrumbs", "85g ready-to-eat dried apricots, chopped", "50g chestnut, canned or vacuum-packed, chopped", "2 tsp chopped fresh or 1tsp dried thyme", "100g cranberries, fresh or frozen", "500g boneless, skinless chicken breasts", "500g pack ready-made shortcrust pastry", "beaten egg, to glaze"], "Method": ["Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.", "Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.", "Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.", "Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles."]} +{"Name": "Simmer-&-stir Christmas cake", "url": "https://www.bbcgoodfood.com/recipes/1160/simmerandstir-christmas-cake", "Description": "An easy-to-make alternative to traditional Christmas cakes which requires no beating", "Author": "Mary Cadogan", "Ingredients": ["175g butter, chopped", "200g dark muscovado sugar", "750g luxury mixed dried fruit (one that includes mixed peel and glac\u00e9 cherries)", "finely grated zest and juice of 1 orange", "finely grated zest of 1 lemon", "100ml/3\u00bd fl oz cherry brandy or brandy plus 4tbsp more", "85g macadamia nut", "3 large eggs, lightly beaten", "85g ground almond", "200g plain flour", "\u00bd tsp baking powder", "1 tsp ground mixed spice", "1 tsp ground cinnamon", "\u00bc tsp ground allspice"], "Method": ["Put the butter, sugar, fruit, zests, juice and 100ml/3\u00bdfl oz brandy in a large pan. Bring slowly to the boil, stirring until the butter has melted. Reduce the heat and bubble for 10 minutes, stirring occasionally.", "Remove the pan from the heat and leave to cool for 30 minutes.", "Meanwhile, preheat the oven to 150C/gas 2/ fan 130C and line a 20cm round cake tin. Toast the nuts in a dry frying pan, tossing them until evenly browned, or in the oven for 8-10 minutes - keep an eye on them as they burn easily. When they are cool, chop roughly. Stir the eggs, nuts and ground almonds into the fruit mixture and mix well. Sift the flour, baking powder and spices into the pan. Stir in gently, until there are no traces of flour left.", "Spoon the mixture into the tin and smooth it down evenly - you will find this is easiest with the back of a metal spoon which has been dipped into boiling water.", "Bake for 45 minutes, then turn down the heat to 140C/gas 1/ fan120C and cook for a further 1-1\u00bc hours (about a further 1\u00be hours if you have a gas oven) until the cake is dark golden in appearance and firm to the touch. Cover the top of the cake with foil if it starts to darken too much. To check the cake is done, insert a fine skewer into the centre - if it comes out clean, the cake is cooked.", "Make holes all over the warm cake with a fine skewer and spoon the extra 4tbsp brandy over the holes until it has all soaked in. Leave the cake to cool in the tin. When it's cold, remove it from the tin, peel off the lining paper, then wrap first in baking parchment and then in foil. The cake will keep in a cupboard for up to three months or you can freeze it for six months."]} +{"Name": "Christmas cupcakes", "url": "https://www.bbcgoodfood.com/recipes/72622/christmas-cupcakes", "Description": "These beautiful and classy little cakes make lovely gifts, and kids will enjoy decorating them too", "Author": "Sara Buenfeld", "Ingredients": ["200g dark muscovado sugar", "175g butter, chopped", "700g luxury mixed dried fruit", "50g glac\u00e9 cherries", "2 tsp grated fresh root ginger", "zest and juice 1 orange", "100ml dark rum, brandy or orange juice", "85g/3oz pecannuts, roughly chopped", "3 large eggs, beaten", "85g ground almond", "200g plain flour", "\u00bd tsp baking powder", "1 tsp mixed spice", "1 tsp cinnamon", "400g pack ready-rolled marzipan(we used Dr Oetker)", "4 tbsp warm apricotjam or shredless marmalade", "500g pack fondant icingsugar", "icing sugar, for dusting", "6 gold and 6 silver muffincases", "6 gold and 6 silver sugared almonds", "snowflake sprinkles"], "Method": ["Tip the sugar, butter, dried fruit, whole cherries, ginger, orange zest and juice into a large pan. Pour over the rum, brandy or juice, then put on the heat and slowly bring to the boil, stirring frequently to melt the butter. Reduce the heat and bubble gently, uncovered for 10 mins, stirring every now and again to make sure the mixture doesn\u2019t catch on the bottom of the pan. Set aside for 30 mins to cool.", "Stir the nuts, eggs and ground almonds into the fruit, then sift in the flour, baking powder and spices. Stir everything together gently but thoroughly. Your batter is ready.", "Heat oven to 150C/130C fan/gas 2. Scoop the cake mix into 12 deep muffin cases (an ice-cream scoop works well), then level tops with a spoon dipped in hot water. Bake for 35-45 mins until golden and just firm to touch. A skewer inserted should come out clean. Cool on a wire rack.", "Unravel the marzipan onto a work surface lightly dusted with icing sugar. Stamp out 12 rounds, 6cm across. Brush the cake tops with apricot jam, top with a marzipan round and press down lightly.", "Make up the fondant icing to a spreading consistency, then swirl on top of each cupcake. Decorate with sugared almonds and snowflakes, then leave to set. Will keep in a tin for 3 weeks."]} +{"Name": "Christmas buns", "url": "https://www.bbcgoodfood.com/recipes/1803633/christmas-buns", "Description": "Paul Hollywood's fruit rolls can be made ahead then heated up before adding a glossy glaze and citrus icing", "Author": "Paul Hollywood", "Ingredients": ["500g strong white flour, plus extra for dusting", "7g sachet fast-action dried yeast", "300ml milk", "40g unsalted butter, softened at room temperature", "1 egg", "vegetable oil, for greasing", "25g unsalted butter, melted", "75g soft brown sugar", "2 tsp ground cinnamon", "100g dried cranberries", "100g chopped dried apricot", "50g caster sugar", "zest 1 lemon", "200g icing sugar"], "Method": ["Put the flour and 1 tsp salt into a large bowl. Make a well in the centre and add the yeast. Meanwhile, warm the milk and butter in a pan until the butter melts and the mixture is lukewarm. Add the milk mixture and egg to the flour mixture and stir until the contents come together as a soft dough (add extra flour if you need to).", "Tip the dough onto a well-floured surface. Knead for 5 mins, adding more flour if necessary, until the dough is smooth, elastic and no longer sticky.", "Lightly oil a bowl with the vegetable oil. Place the dough in the bowl and turn until covered in oil. Cover the bowl with cling film and set aside in a warm place for 1 hr or until doubled in size. Lightly grease a baking sheet and set aside.", "For the filling, knock the dough back to its original size and turn out onto a lightly floured surface. Roll it into a 1cm-thick rectangle. Brush all over with the melted butter, then sprinkle over the sugar, cinnamon and fruit.", "Roll up the dough into a tight cylinder, cut into 9 x 4cm slices and position on the prepared baking sheet, leaving a little space between. Cover with a tea towel and set aside to rise for 30 mins.", "Heat oven to 190C/170C fan/gas 5. Bake the buns for 20-25 mins or until risen and golden brown. Meanwhile, melt the glaze sugar with 4 tbsp water until syrupy.", "Remove from oven and glaze. Set aside to cool on a wire rack. Once cool, mix the zest and icing sugar with about 2 tbsp water to drizzle over the buns. Serve."]} +{"Name": "Christmas cupcakes", "url": "https://www.bbcgoodfood.com/recipes/981634/christmas-cupcakes", "Description": "Made these for the second time today, and I have to say they turned out great! I've got large muffin tins and the mixture made 15 muffins, will definetely make these again at christmas time and decorate festively.", "Author": "Barney Desmazery", "Ingredients": ["280g self-raising flour", "175g golden caster sugar", "175g unsalted butter, very soft", "150g pot fat-free natural yogurt", "1 tsp vanilla extract", "3 eggs", "85g unsalted butter, softened", "1 tsp vanilla extract", "200g icing sugar, sifted", "natural green food colouring(for Christmas trees), sweets, sprinkles and white chocolate stars", "milk and white chocolatebuttons and natural colouring icing pens, available at Asda"], "Method": ["Heat oven to 190C/170 fan/gas 5 and line a 12-hole muffin tin with cake cases. Put all the cake ingredients into a bowl and mix with a whisk until smooth. Spoon the mix into the cases, bake for 25 mins until golden and risen and a skewer comes out clean. Cool on a wire rack.", "For the frosting, beat the butter, vanilla extract and icing sugar until pale and creamy and completely combined. To make snowmen, reindeer and Christmas puddings, first spread the icing over the top of each cake. Then lay the chocolate buttons on top, slicing some buttons into quarters to make ears and hats. Finally, use icing pens for the details. For the Christmas tree, colour the icing with green food colouring and pipe onto the cakes using a star-shaped nozzle, decorate with sweets, sprinkles and white chocolate stars."]} +{"Name": "Christmas slaw", "url": "https://www.bbcgoodfood.com/recipes/890635/christmas-slaw", "Description": "A nutty winter salad which is superhealthy, quick to prepare and finished with a light maple syrup dressing", "Author": "Good Food", "Ingredients": ["2 carrots, halved", "\u00bd white cabbage, shredded", "100g pecans, roughly chopped", "bunch spring onions, sliced", "2 red peppers, deseeded and sliced", "2 tbsp maple syrup", "2 tsp Dijon mustard", "8 tbsp olive oil", "4 tbsp cider vinegar"], "Method": ["Peel strips from the carrots using a vegetable peeler, then mix with the other salad ingredients in a large bowl. Combine all the dressing ingredients in a jam jar, season, then put the lid on and shake well. Toss through the salad when you\u2019re ready to eat it. The salad and dressing will keep separately in the fridge for up to four days."]} +{"Name": "Christmas mess", "url": "https://www.bbcgoodfood.com/recipes/2806664/christmas-mess", "Description": "Delicious and a synch to make! Have made this a couple of times as a dinner party dessert, very pretty as a winter alternative to Eton mess. The fact you use frozen fruits is great, I just used a bog standard pack of frozen mixed berries and added some home made blackberry liqueur. Like other people, I added more cinnamon. Thumbs up!", "Author": "Caroline Hire", "Ingredients": ["600ml double cream", "400g Greek yoghurt", "4 tbsp lemon curd", "1 x 500g bag frozen mixed berries(we used Sainsbury's Black Forest fruits)", "4 tbsp icing sugar", "2 tbsp cassis(optional)", "1 pinch cinnamon", "8 meringuenests"], "Method": ["In a small saucepan gently heat the frozen berries, icing sugar and cinnamon until the sugar has dissolved. Remove from the heat, stir in the cassis, if using, and set aside to cool completely.", "Whip the double cream and Greek yogurt until just holding it\u2019s shape, ripple through the lemon curd. Break the meringue nests into a glass bowl, or 8 individual glasses. Spoon over half the cream, then half the berries. Repeat with the remaining cream and berries. Serve immediately."]} +{"Name": "Christmas brownies", "url": "https://www.bbcgoodfood.com/recipes/christmas-brownies", "Description": "can I made these the day before", "Author": "Miriam Nice", "Ingredients": ["200g unsalted buttercut into cubes, plus extra for greasing", "100g dark chocolate, chopped", "100g milk chocolate, chopped", "3 large eggs", "300g golden caster sugar", "100g plain flour", "50g cocoa powder", "\u00bd tsp mixed spice", "9 sprigs rosemary", "9 glac\u00e9 cherries", "1 egg white", "2 tbsp caster sugar", "4 amaretti biscuits, crushed", "9 chocolate truffles(we used Lindt lindor)", "edible gold lustre spray", "1-2 tsp icing sugarfor dusting", "few chocolate buttons", "edible silver balls"], "Method": ["Grease and line a 20cm x 20cm brownie tin. Heat oven to 180C/160C fan/gas 4. Put the butter and both types of chocolate in a heat proof bowl and either melt in the microwave (in 30 second bursts, stirring after each) or set over a pan of barely simmering water, stirring every now and then until the chocolate has melted.", "Leave the chocolate and butter mixture to cool a little while you whisk the eggs and caster sugar in a large bowl using electric beaters. Once the mixture is pale, fluffy and looks like it\u2019s roughly\u00a0doubled in volume, whisk in the melted chocolate. Fold in the flour, cocoa powder and mixed spice until no pockets of flour remain\u00a0then pour\u00a0into your prepared tin. Level the top\u00a0with a spatula and bake for 20-25 mins. The top should look set and shiny but should be a little wobbly if you gently jostle the tin.", "Leave the brownie to cool completely in the tin then chill in the fridge until set. While the brownie cools brush the rosemary sprigs and glac\u00e9\u00a0cherries with egg white, dab off the excess with kitchen paper then dredge in caster sugar until well coated. Leave to dry on a wire rack. Put the chocolate truffles onto a sheet of baking paper or foil then spray with the edible gold lustre.", "Dust the chilled brownie with icing sugar to create a snowy surface and top with amaretti biscuit pieces then poke the crystalised rosemary sprigs into the surface at random intervals (cut the brownie into pieces and dust on icing sugar first, if you like). Nestle a glac\u00e9 cherry or gold truffle alongside the rosemary sprig then add the buttons and silver balls."]} +{"Name": "Christmas cosmopolitan", "url": "https://www.bbcgoodfood.com/recipes/889643/christmas-cosmopolitan", "Description": "How many servings have you gotten using the above measures?", "Author": "Good Food", "Ingredients": ["500ml vodka", "500ml gingerwine", "1l cranberry juice", "juice 5 limes, keep zest for garnish", "sliced stem ginger"], "Method": ["Mix the vodka and ginger wine in a jug. Stir in the cranberry juice, lime juice and some sliced stem ginger. Garnish with lime zest, if you like."]} +{"Name": "Christmas pizza", "url": "https://www.bbcgoodfood.com/recipes/christmas-pizza", "Description": "Use up leftover roast turkey and sausagemeat stuffing in this new spin on an Italian classic", "Author": "Katy Greenwood", "Ingredients": ["145g pizza basemix", "6 tbsp tomato pasta sauce", "large handful (about 100g) leftover stuffing(a sausage stuffing works well for this)", "large handful (about 100g) leftover cooked turkey, shredded", "100g mozzarella, sliced", "small pack sage, leaves picked", "1 tbsp olive oil"], "Method": ["Heat oven to 220C/200C fan/gas 7. Prepare the pizza base mix following pack instructions. Once rolled out, leave to rest for 10 mins, then top with the pasta sauce.", "Scatter over the stuffing and turkey, then top with the mozzarella. Toss the sage leaves with the oil, then scatter over the pizza, drizzling over any remaining oil. Bake for 10-12 mins until the crust is crisp and the cheese has melted."]} From cf7b9219b7663814f9a08b3b13cbc8c072543fa4 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 4 Nov 2020 13:14:07 +0100 Subject: [PATCH 02/18] Generates DBT models from JSON Schema catalog --- .gitignore | 3 + .../integration_tests/catalog.json | 514 ++++++++++++++++++ .../dbt-transform/models/recipes_test.sql | 7 - .../transform_catalog/transform.py | 250 ++++++++- 4 files changed, 761 insertions(+), 13 deletions(-) create mode 100644 airbyte-integrations/bases/base-normalization/integration_tests/catalog.json delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/recipes_test.sql diff --git a/.gitignore b/.gitignore index 9221564f5034b..7f689e5320765 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ __pycache__ .venv .mypy_cache +# dbt +airbyte-integrations/bases/base-normalization/normalization/dbt-transform/logs/ +airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/generated diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json b/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json new file mode 100644 index 0000000000000..7cb3dd59836db --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json @@ -0,0 +1,514 @@ +{ + "streams": [ + { + "name": "one_recipe", + "json_schema": + { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "The root schema", + "description": "The root schema comprises the entire JSON document.", + "default": {}, + "examples": [ + { + "Name": "Christmas pie", + "url": "https://www.bbcgoodfood.com/recipes/2793/christmas-pie", + "Description": "Combine a few key Christmas flavours here to make a pie that both children and adults will adore", + "Author": "Mary Cadogan", + "Ingredients": [ + { + "quantity": "2 tbsp", + "ingredient": "olive oil", + "nutritionfacts": { + "calories": 119, + "fat": "13.5g" + } + }, + { + "quantity": "knob", + "ingredient": "butter", + "nutritionfacts": { + "calories": 102, + "fat": "11.5g" + } + }, + { + "quantity": "1", + "ingredient": "onion", + "preparation": "finely chopped", + "nutritionfacts": { + "calories": 102, + "fat": "11.5g" + } + }, + { + "quantity": "500g", + "ingredient": "sausagemeat or skinned sausages", + "nutritionfacts": { + "calories": 268, + "fat": "18g" + } + }, + { + "quantity": "1", + "ingredient": "lemon", + "preparation": "grated zest", + "nutritionfacts": { + "calories": 13 + } + }, + { + "quantity": "100g", + "ingredient": "fresh white breadcrumbs" + }, + { + "quantity": "85g", + "ingredient": "ready-to-eat dried apricots", + "preparation": "chopped", + "nutritionfacts": { + "calories": 34, + "fat": "0.27g" + } + }, + { + "quantity": "58g", + "ingredient": "chestnut, canned or vacuum-packed", + "preparation": "chopped", + "nutritionfacts": { + "calories": 77, + "fat": "1g" + } + }, + { + "quantity": "2 tsp", + "ingredient": "fresh or dried thyme", + "preparation": "chopped" + }, + { + "quantity": "100g", + "ingredient": "cranberries, fresh or frozen" + }, + { + "quantity": "500g", + "ingredient": "boneless, skinless chicken breasts", + "nutritionfacts": { + "calories": 284, + "fat": "6.2g" + } + }, + { + "quantity": "500g", + "ingredient": "pack ready-made shortcrust pastry" + }, + { + "quantity": "1", + "ingredient": "beaten egg", + "preparation": "to glaze", + "nutritionfacts": { + "calories": 75, + "fat": "5g" + } + } + ], + "Method": [ + "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", + "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.", + "Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.", + "Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.", + "Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles." + ] + } + ], + "required": [ + "Name", + "url", + "Description", + "Author", + "Ingredients", + "Method" + ], + "properties": { + "Name": { + "$id": "#/properties/Name", + "type": "string", + "title": "The Name schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "Christmas pie" + ] + }, + "url": { + "$id": "#/properties/url", + "type": "string", + "title": "The url schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "https://www.bbcgoodfood.com/recipes/2793/christmas-pie" + ] + }, + "Description": { + "$id": "#/properties/Description", + "type": "string", + "title": "The Description schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "Combine a few key Christmas flavours here to make a pie that both children and adults will adore" + ] + }, + "Author": { + "$id": "#/properties/Author", + "type": "string", + "title": "The Author schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "Mary Cadogan" + ] + }, + "Ingredients": { + "$id": "#/properties/Ingredients", + "type": "array", + "title": "The Ingredients schema", + "description": "An explanation about the purpose of this instance.", + "default": [], + "examples": [ + [ + { + "quantity": "2 tbsp", + "ingredient": "olive oil", + "nutritionfacts": { + "calories": 119, + "fat": "13.5g" + } + }, + { + "quantity": "knob", + "ingredient": "butter", + "nutritionfacts": { + "calories": 102, + "fat": "11.5g" + } + } + ] + ], + "additionalItems": true, + "items": { + "$id": "#/properties/Ingredients/items", + "anyOf": [ + { + "$id": "#/properties/Ingredients/items/anyOf/0", + "type": "object", + "title": "The first anyOf schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "quantity": "2 tbsp", + "ingredient": "olive oil", + "nutritionfacts": { + "calories": 119, + "fat": "13.5g" + } + } + ], + "required": [ + "quantity", + "ingredient", + "nutritionfacts" + ], + "properties": { + "quantity": { + "$id": "#/properties/Ingredients/items/anyOf/0/properties/quantity", + "type": "string", + "title": "The quantity schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "2 tbsp" + ] + }, + "ingredient": { + "$id": "#/properties/Ingredients/items/anyOf/0/properties/ingredient", + "type": "string", + "title": "The ingredient schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "olive oil" + ] + }, + "nutritionfacts": { + "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts", + "type": "object", + "title": "The nutritionfacts schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "calories": 119, + "fat": "13.5g" + } + ], + "required": [ + "calories", + "fat" + ], + "properties": { + "calories": { + "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/calories", + "type": "integer", + "title": "The calories schema", + "description": "An explanation about the purpose of this instance.", + "default": 0, + "examples": [ + 119 + ] + }, + "fat": { + "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/fat", + "type": "string", + "title": "The fat schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "13.5g" + ] + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + { + "$id": "#/properties/Ingredients/items/anyOf/1", + "type": "object", + "title": "The second anyOf schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "quantity": "1", + "ingredient": "onion", + "preparation": "finely chopped", + "nutritionfacts": { + "calories": 102, + "fat": "11.5g" + } + } + ], + "required": [ + "quantity", + "ingredient", + "preparation", + "nutritionfacts" + ], + "properties": { + "quantity": { + "$id": "#/properties/Ingredients/items/anyOf/1/properties/quantity", + "type": "string", + "title": "The quantity schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "1" + ] + }, + "ingredient": { + "$id": "#/properties/Ingredients/items/anyOf/1/properties/ingredient", + "type": "string", + "title": "The ingredient schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "onion" + ] + }, + "preparation": { + "$id": "#/properties/Ingredients/items/anyOf/1/properties/preparation", + "type": "string", + "title": "The preparation schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "finely chopped" + ] + }, + "nutritionfacts": { + "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts", + "type": "object", + "title": "The nutritionfacts schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "calories": 102, + "fat": "11.5g" + } + ], + "required": [ + "calories", + "fat" + ], + "properties": { + "calories": { + "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/calories", + "type": "integer", + "title": "The calories schema", + "description": "An explanation about the purpose of this instance.", + "default": 0, + "examples": [ + 102 + ] + }, + "fat": { + "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/fat", + "type": "string", + "title": "The fat schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "11.5g" + ] + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + { + "$id": "#/properties/Ingredients/items/anyOf/2", + "type": "object", + "title": "The third anyOf schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "quantity": "100g", + "ingredient": "fresh white breadcrumbs" + } + ], + "required": [ + "quantity", + "ingredient" + ], + "properties": { + "quantity": { + "$id": "#/properties/Ingredients/items/anyOf/2/properties/quantity", + "type": "string", + "title": "The quantity schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "100g" + ] + }, + "ingredient": { + "$id": "#/properties/Ingredients/items/anyOf/2/properties/ingredient", + "type": "string", + "title": "The ingredient schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "fresh white breadcrumbs" + ] + } + }, + "additionalProperties": true + }, + { + "$id": "#/properties/Ingredients/items/anyOf/3", + "type": "object", + "title": "The fourth anyOf schema", + "description": "An explanation about the purpose of this instance.", + "default": {}, + "examples": [ + { + "quantity": "2 tsp", + "ingredient": "fresh or dried thyme", + "preparation": "chopped" + } + ], + "required": [ + "quantity", + "ingredient", + "preparation" + ], + "properties": { + "quantity": { + "$id": "#/properties/Ingredients/items/anyOf/3/properties/quantity", + "type": "string", + "title": "The quantity schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "2 tsp" + ] + }, + "ingredient": { + "$id": "#/properties/Ingredients/items/anyOf/3/properties/ingredient", + "type": "string", + "title": "The ingredient schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "fresh or dried thyme" + ] + }, + "preparation": { + "$id": "#/properties/Ingredients/items/anyOf/3/properties/preparation", + "type": "string", + "title": "The preparation schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "chopped" + ] + } + }, + "additionalProperties": true + } + ] + } + }, + "Method": { + "$id": "#/properties/Method", + "type": "array", + "title": "The Method schema", + "description": "An explanation about the purpose of this instance.", + "default": [], + "examples": [ + [ + "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", + "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins." + ] + ], + "additionalItems": true, + "items": { + "$id": "#/properties/Method/items", + "anyOf": [ + { + "$id": "#/properties/Method/items/anyOf/0", + "type": "string", + "title": "The first anyOf schema", + "description": "An explanation about the purpose of this instance.", + "default": "", + "examples": [ + "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", + "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins." + ] + } + ] + } + } + }, + "additionalProperties": true + } + } + ] +} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/recipes_test.sql b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/recipes_test.sql deleted file mode 100644 index 09701a9c533f1..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/recipes_test.sql +++ /dev/null @@ -1,7 +0,0 @@ -SELECT - * -FROM - {{ SOURCE( - 'data', - 'recipes_json' - ) }} \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py index a5faa8ef90158..9b3fd7102b4ae 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py @@ -22,15 +22,253 @@ SOFTWARE. """ +import argparse +import json import os - -print(os) +from typing import Tuple, Union, Optional class TransformCatalog: - def run(self): - print("running catalog transform") + config: dict = {} + + def run(self, args): + self.parse(args) + catalog = self.read_json_catalog() + result = generate_dbt_model(catalog=catalog, json_col="json_blob", from_table="`airbytesandbox.data.one_recipe_json`") + self.output_sql_models(result) + + def parse(self, args): + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("--catalog", type=str, required=True, help="path to Catalog (JSON Schema) file") + parser.add_argument("--out", type=str, required=True, help="path to output generated DBT Models to") + parsed_args = parser.parse_args(args) + self.config = { + "catalog": parsed_args.catalog, + "output_path": parsed_args.out, + } + + def read_json_catalog(self) -> dict: + input_path = self.config["catalog"] + with open(input_path, "r") as file: + contents = file.read() + return json.loads(contents) + + def output_sql_models(self, result: dict): + output = self.config["output_path"] + if result: + if not os.path.exists(output): + os.makedirs(output) + for file, sql in result.items(): + print(f"Generating {file.lower()}.sql in {output}") + with open(os.path.join(output, f"{file}.sql").lower(), "w") as f: + f.write(sql) + + +def is_string(property_type) -> bool: + return property_type == "string" or "string" in property_type + + +def is_integer(property_type) -> bool: + return property_type == "integer" or "integer" in property_type + + +def is_boolean(property_type) -> bool: + return property_type == "boolean" or "boolean" in property_type + + +def is_array(property_type) -> bool: + return property_type == "array" or "array" in property_type + + +def is_object(property_type) -> bool: + return property_type == "object" or "object" in property_type + + +def find_combining_schema(properties: dict): + return set(properties).intersection({"anyOf", "oneOf", "allOf"}) + + +def json_extract_base_property(path: str, json_col: str, name: str, definition: dict) -> Optional[str]: + current = ".".join([path, name]) + if "type" not in definition: + return None + elif is_string(definition["type"]): + return f"cast(json_extract_scalar({json_col}, '{current}') as string) as {name}" + elif is_integer(definition["type"]): + return f"cast(json_extract_scalar({json_col}, '{current}') as int64) as {name}" + elif is_boolean(definition["type"]): + return f"cast(json_extract_scalar({json_col}, '{current}') as boolean) as {name}" + else: + return None + + +def json_extract_nested_property(path: str, json_col: str, name: str, definition: dict) -> Union[Tuple[None, None], Tuple[str, str]]: + current = ".".join([path, name]) + if definition is None or "type" not in definition: + return None, None + elif is_array(definition["type"]): + return f"json_extract_array({json_col}, '{current}') as {name}", f"cross join unnest({name}) as {name}" + elif is_object(definition["type"]): + return f"json_extract({json_col}, '{current}') as {name}", "" + else: + return None, None + + +def select_table(table: str, columns="*"): + return f"\nselect {columns} from {table}" + + +def extract_node_properties(path: str, json_col: str, properties: dict) -> dict: + result = {} + if properties: + for field in properties.keys(): + sql_field = json_extract_base_property(path=path, json_col=json_col, name=field, definition=properties[field]) + if sql_field: + result[field] = sql_field + return result + + +def find_properties_object(path: str, field: str, properties) -> dict: + if isinstance(properties, str) or isinstance(properties, int): + return {} + else: + if "items" in properties: + return find_properties_object(path, field, properties["items"]) + elif "properties" in properties: + # we found a properties object + return {field: properties["properties"]} + elif "type" in properties and json_extract_base_property(path=path, json_col="", name="", definition=properties): + # we found a basic type + return {field: None} + elif isinstance(properties, dict): + for key in properties.keys(): + if not json_extract_base_property(path, "", key, properties[key]): + child = find_properties_object(path, key, properties[key]) + if child: + return child + elif isinstance(properties, list): + for item in properties: + child = find_properties_object(path=path, field=field, properties=item) + if child: + return child + return {} + + +def extract_nested_properties(path: str, field: str, properties: dict) -> dict: + result = {} + if properties: + for key in properties.keys(): + combining = find_combining_schema(properties[key]) + if combining: + # skip combining schemas + for combo in combining: + found = find_properties_object(path=f"{path}.{field}.{key}", field=key, properties=properties[key][combo]) + result.update(found) + elif "type" not in properties[key]: + pass + elif is_array(properties[key]["type"]): + combining = find_combining_schema(properties[key]["items"]) + if combining: + # skip combining schemas + for combo in combining: + found = find_properties_object(path=f"{path}.{key}", field=key, properties=properties[key]["items"][combo]) + result.update(found) + else: + found = find_properties_object(path=f"{path}.{key}", field=key, properties=properties[key]["items"]) + result.update(found) + elif is_object(properties[key]["type"]): + found = find_properties_object(path=f"{path}.{key}", field=key, properties=properties[key]) + result.update(found) + return result + + +def process_node(path: str, json_col: str, name: str, properties: dict, from_table: str = "", previous="with ", inject_cols="") -> dict: + result = {} + if previous == "with ": + prefix = previous + else: + prefix = previous + "," + node_properties = extract_node_properties(path=path, json_col=json_col, properties=properties) + node_columns = ",\n ".join([sql for sql in node_properties.values()]) + # FIXME: use DBT macros to be cross_db compatible instead + hash_node_columns = ( + "coalesce(cast(" + + ' as string), ""),\n coalesce(cast('.join([column for column in node_properties.keys()]) + + ' as string), "")' + ) + node_sql = f"""{prefix} +{name}_node as ( + select + {inject_cols} + {node_columns} + from {from_table} +), +{name}_with_id as ( + select + *, + to_hex(md5(concat( + {hash_node_columns} + ))) as _{name}_hashid + from {name}_node +)""" + # SQL Query for current node's basic properties + result[name] = node_sql + select_table(f"{name}_with_id") + + children_columns = extract_nested_properties(path=path, field=name, properties=properties) + if children_columns: + for col in children_columns.keys(): + child_col, join_child_table = json_extract_nested_property(path=path, json_col=json_col, name=col, definition=properties[col]) + child_sql = f"""{prefix} +{name}_node as ( + select + {child_col}, + {node_columns} + from {from_table} +), +{name}_with_id as ( + select + to_hex(md5(concat( + {hash_node_columns} + ))) as _{name}_hashid, + {col} + from {name}_node + {join_child_table} +)""" + if children_columns[col]: + children = process_node( + path="$", + json_col=col, + name=f"{name}_{col}", + properties=children_columns[col], + from_table=f"{name}_with_id", + previous=child_sql, + inject_cols=f"_{name}_hashid as _{name}_foreign_hashid,", + ) + result.update(children) + else: + # SQL Query for current node's basic properties + result[f"{name}_{col}"] = child_sql + select_table( + f"{name}_with_id", + columns=f""" + _{name}_hashid as _{name}_foreign_hashid, + {col} +""", + ) + return result + + +def generate_dbt_model(catalog: dict, json_col: str, from_table: str) -> dict: + result = {} + for obj in catalog["streams"]: + name = obj["name"] + properties = {} + if "json_schema" in obj: + properties = obj["json_schema"]["properties"] + elif "schema" in obj: + properties = obj["schema"]["properties"] + result.update(process_node(path="$", json_col=json_col, name=name, properties=properties, from_table=from_table)) + return result -def main(): - TransformCatalog().run() +def main(args=None): + TransformCatalog().run(args) From bb5f6eba43aac22ba42a8b85b9c32a1524dc94e1 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 4 Nov 2020 13:46:03 +0100 Subject: [PATCH 03/18] Add Doc on how to run transformation of catalog --- .../one_recipe.json} | 0 .../normalization/normalization.ipynb | 5984 ----------------- .../normalization/normalization.txt | 264 - .../dbt-transform/sample_files/catalog.json | 514 -- .../sample_files/catalog_exchangerateapi.json | 46 - .../sample_files/catalog_file.json | 34 - .../sample_files/catalog_github.json | 169 - .../sample_files/catalog_google-sheets.json | 46 - .../sample_files/catalog_hubspot.json | 79 - .../sample_files/catalog_salesforce.json | 22 - .../sample_files/catalog_stripe.json | 863 --- .../sample_files/one_recipe.json | 108 - .../dbt-transform/sample_files/profiles.yml | 40 - .../dbt-transform/sample_files/recipes.json | 10 - .../transform_catalog/transform.py | 53 +- 15 files changed, 35 insertions(+), 8197 deletions(-) rename airbyte-integrations/bases/base-normalization/{normalization/dbt-transform/sample_files/one_line_recipe.json => integration_tests/one_recipe.json} (100%) delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.ipynb delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.txt delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog.json delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_exchangerateapi.json delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_file.json delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_github.json delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_google-sheets.json delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_hubspot.json delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_salesforce.json delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_stripe.json delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_recipe.json delete mode 100755 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/profiles.yml delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/recipes.json diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_line_recipe.json b/airbyte-integrations/bases/base-normalization/integration_tests/one_recipe.json similarity index 100% rename from airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_line_recipe.json rename to airbyte-integrations/bases/base-normalization/integration_tests/one_recipe.json diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.ipynb b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.ipynb deleted file mode 100644 index 9b0fa595cf9ea..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.ipynb +++ /dev/null @@ -1,5984 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import json" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "From catalog ../sample_files/catalog.json:\n", - "--------------------\n", - "{\n", - " \"streams\": [\n", - " {\n", - " \"json_schema\": {\n", - " \"$id\": \"http://example.com/example.json\",\n", - " \"$schema\": \"http://json-schema.org/draft-07/schema\",\n", - " \"additionalProperties\": true,\n", - " \"default\": {},\n", - " \"description\": \"The root schema comprises the entire JSON document.\",\n", - " \"examples\": [\n", - " {\n", - " \"Author\": \"Mary Cadogan\",\n", - " \"Description\": \"Combine a few key Christmas flavours here to make a pie that both children and adults will adore\",\n", - " \"Ingredients\": [\n", - " {\n", - " \"ingredient\": \"olive oil\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 119,\n", - " \"fat\": \"13.5g\"\n", - " },\n", - " \"quantity\": \"2 tbsp\"\n", - " },\n", - " {\n", - " \"ingredient\": \"butter\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 102,\n", - " \"fat\": \"11.5g\"\n", - " },\n", - " \"quantity\": \"knob\"\n", - " },\n", - " {\n", - " \"ingredient\": \"onion\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 102,\n", - " \"fat\": \"11.5g\"\n", - " },\n", - " \"preparation\": \"finely chopped\",\n", - " \"quantity\": \"1\"\n", - " },\n", - " {\n", - " \"ingredient\": \"sausagemeat or skinned sausages\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 268,\n", - " \"fat\": \"18g\"\n", - " },\n", - " \"quantity\": \"500g\"\n", - " },\n", - " {\n", - " \"ingredient\": \"lemon\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 13\n", - " },\n", - " \"preparation\": \"grated zest\",\n", - " \"quantity\": \"1\"\n", - " },\n", - " {\n", - " \"ingredient\": \"fresh white breadcrumbs\",\n", - " \"quantity\": \"100g\"\n", - " },\n", - " {\n", - " \"ingredient\": \"ready-to-eat dried apricots\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 34,\n", - " \"fat\": \"0.27g\"\n", - " },\n", - " \"preparation\": \"chopped\",\n", - " \"quantity\": \"85g\"\n", - " },\n", - " {\n", - " \"ingredient\": \"chestnut, canned or vacuum-packed\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 77,\n", - " \"fat\": \"1g\"\n", - " },\n", - " \"preparation\": \"chopped\",\n", - " \"quantity\": \"58g\"\n", - " },\n", - " {\n", - " \"ingredient\": \"fresh or dried thyme\",\n", - " \"preparation\": \"chopped\",\n", - " \"quantity\": \"2 tsp\"\n", - " },\n", - " {\n", - " \"ingredient\": \"cranberries, fresh or frozen\",\n", - " \"quantity\": \"100g\"\n", - " },\n", - " {\n", - " \"ingredient\": \"boneless, skinless chicken breasts\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 284,\n", - " \"fat\": \"6.2g\"\n", - " },\n", - " \"quantity\": \"500g\"\n", - " },\n", - " {\n", - " \"ingredient\": \"pack ready-made shortcrust pastry\",\n", - " \"quantity\": \"500g\"\n", - " },\n", - " {\n", - " \"ingredient\": \"beaten egg\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 75,\n", - " \"fat\": \"5g\"\n", - " },\n", - " \"preparation\": \"to glaze\",\n", - " \"quantity\": \"1\"\n", - " }\n", - " ],\n", - " \"Method\": [\n", - " \"Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.\",\n", - " \"Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.\",\n", - " \"Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.\",\n", - " \"Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.\",\n", - " \"Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles.\"\n", - " ],\n", - " \"Name\": \"Christmas pie\",\n", - " \"url\": \"https://www.bbcgoodfood.com/recipes/2793/christmas-pie\"\n", - " }\n", - " ],\n", - " \"properties\": {\n", - " \"Author\": {\n", - " \"$id\": \"#/properties/Author\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"Mary Cadogan\"\n", - " ],\n", - " \"title\": \"The Author schema\",\n", - " \"type\": \"string\"\n", - " },\n", - " \"Description\": {\n", - " \"$id\": \"#/properties/Description\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"Combine a few key Christmas flavours here to make a pie that both children and adults will adore\"\n", - " ],\n", - " \"title\": \"The Description schema\",\n", - " \"type\": \"string\"\n", - " },\n", - " \"Ingredients\": {\n", - " \"$id\": \"#/properties/Ingredients\",\n", - " \"additionalItems\": true,\n", - " \"default\": [],\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " [\n", - " {\n", - " \"ingredient\": \"olive oil\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 119,\n", - " \"fat\": \"13.5g\"\n", - " },\n", - " \"quantity\": \"2 tbsp\"\n", - " },\n", - " {\n", - " \"ingredient\": \"butter\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 102,\n", - " \"fat\": \"11.5g\"\n", - " },\n", - " \"quantity\": \"knob\"\n", - " }\n", - " ]\n", - " ],\n", - " \"items\": {\n", - " \"$id\": \"#/properties/Ingredients/items\",\n", - " \"anyOf\": [\n", - " {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/0\",\n", - " \"additionalProperties\": true,\n", - " \"default\": {},\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " {\n", - " \"ingredient\": \"olive oil\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 119,\n", - " \"fat\": \"13.5g\"\n", - " },\n", - " \"quantity\": \"2 tbsp\"\n", - " }\n", - " ],\n", - " \"properties\": {\n", - " \"ingredient\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/0/properties/ingredient\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"olive oil\"\n", - " ],\n", - " \"title\": \"The ingredient schema\",\n", - " \"type\": \"string\"\n", - " },\n", - " \"nutritionfacts\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts\",\n", - " \"additionalProperties\": true,\n", - " \"default\": {},\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " {\n", - " \"calories\": 119,\n", - " \"fat\": \"13.5g\"\n", - " }\n", - " ],\n", - " \"properties\": {\n", - " \"calories\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/calories\",\n", - " \"default\": 0,\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " 119\n", - " ],\n", - " \"title\": \"The calories schema\",\n", - " \"type\": \"integer\"\n", - " },\n", - " \"fat\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/fat\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"13.5g\"\n", - " ],\n", - " \"title\": \"The fat schema\",\n", - " \"type\": \"string\"\n", - " }\n", - " },\n", - " \"required\": [\n", - " \"calories\",\n", - " \"fat\"\n", - " ],\n", - " \"title\": \"The nutritionfacts schema\",\n", - " \"type\": \"object\"\n", - " },\n", - " \"quantity\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/0/properties/quantity\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"2 tbsp\"\n", - " ],\n", - " \"title\": \"The quantity schema\",\n", - " \"type\": \"string\"\n", - " }\n", - " },\n", - " \"required\": [\n", - " \"quantity\",\n", - " \"ingredient\",\n", - " \"nutritionfacts\"\n", - " ],\n", - " \"title\": \"The first anyOf schema\",\n", - " \"type\": \"object\"\n", - " },\n", - " {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/1\",\n", - " \"additionalProperties\": true,\n", - " \"default\": {},\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " {\n", - " \"ingredient\": \"onion\",\n", - " \"nutritionfacts\": {\n", - " \"calories\": 102,\n", - " \"fat\": \"11.5g\"\n", - " },\n", - " \"preparation\": \"finely chopped\",\n", - " \"quantity\": \"1\"\n", - " }\n", - " ],\n", - " \"properties\": {\n", - " \"ingredient\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/1/properties/ingredient\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"onion\"\n", - " ],\n", - " \"title\": \"The ingredient schema\",\n", - " \"type\": \"string\"\n", - " },\n", - " \"nutritionfacts\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts\",\n", - " \"additionalProperties\": true,\n", - " \"default\": {},\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " {\n", - " \"calories\": 102,\n", - " \"fat\": \"11.5g\"\n", - " }\n", - " ],\n", - " \"properties\": {\n", - " \"calories\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/calories\",\n", - " \"default\": 0,\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " 102\n", - " ],\n", - " \"title\": \"The calories schema\",\n", - " \"type\": \"integer\"\n", - " },\n", - " \"fat\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/fat\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"11.5g\"\n", - " ],\n", - " \"title\": \"The fat schema\",\n", - " \"type\": \"string\"\n", - " }\n", - " },\n", - " \"required\": [\n", - " \"calories\",\n", - " \"fat\"\n", - " ],\n", - " \"title\": \"The nutritionfacts schema\",\n", - " \"type\": \"object\"\n", - " },\n", - " \"preparation\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/1/properties/preparation\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"finely chopped\"\n", - " ],\n", - " \"title\": \"The preparation schema\",\n", - " \"type\": \"string\"\n", - " },\n", - " \"quantity\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/1/properties/quantity\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"1\"\n", - " ],\n", - " \"title\": \"The quantity schema\",\n", - " \"type\": \"string\"\n", - " }\n", - " },\n", - " \"required\": [\n", - " \"quantity\",\n", - " \"ingredient\",\n", - " \"preparation\",\n", - " \"nutritionfacts\"\n", - " ],\n", - " \"title\": \"The second anyOf schema\",\n", - " \"type\": \"object\"\n", - " },\n", - " {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/2\",\n", - " \"additionalProperties\": true,\n", - " \"default\": {},\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " {\n", - " \"ingredient\": \"fresh white breadcrumbs\",\n", - " \"quantity\": \"100g\"\n", - " }\n", - " ],\n", - " \"properties\": {\n", - " \"ingredient\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/2/properties/ingredient\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"fresh white breadcrumbs\"\n", - " ],\n", - " \"title\": \"The ingredient schema\",\n", - " \"type\": \"string\"\n", - " },\n", - " \"quantity\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/2/properties/quantity\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"100g\"\n", - " ],\n", - " \"title\": \"The quantity schema\",\n", - " \"type\": \"string\"\n", - " }\n", - " },\n", - " \"required\": [\n", - " \"quantity\",\n", - " \"ingredient\"\n", - " ],\n", - " \"title\": \"The third anyOf schema\",\n", - " \"type\": \"object\"\n", - " },\n", - " {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/3\",\n", - " \"additionalProperties\": true,\n", - " \"default\": {},\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " {\n", - " \"ingredient\": \"fresh or dried thyme\",\n", - " \"preparation\": \"chopped\",\n", - " \"quantity\": \"2 tsp\"\n", - " }\n", - " ],\n", - " \"properties\": {\n", - " \"ingredient\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/3/properties/ingredient\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"fresh or dried thyme\"\n", - " ],\n", - " \"title\": \"The ingredient schema\",\n", - " \"type\": \"string\"\n", - " },\n", - " \"preparation\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/3/properties/preparation\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"chopped\"\n", - " ],\n", - " \"title\": \"The preparation schema\",\n", - " \"type\": \"string\"\n", - " },\n", - " \"quantity\": {\n", - " \"$id\": \"#/properties/Ingredients/items/anyOf/3/properties/quantity\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"2 tsp\"\n", - " ],\n", - " \"title\": \"The quantity schema\",\n", - " \"type\": \"string\"\n", - " }\n", - " },\n", - " \"required\": [\n", - " \"quantity\",\n", - " \"ingredient\",\n", - " \"preparation\"\n", - " ],\n", - " \"title\": \"The fourth anyOf schema\",\n", - " \"type\": \"object\"\n", - " }\n", - " ]\n", - " },\n", - " \"title\": \"The Ingredients schema\",\n", - " \"type\": \"array\"\n", - " },\n", - " \"Method\": {\n", - " \"$id\": \"#/properties/Method\",\n", - " \"additionalItems\": true,\n", - " \"default\": [],\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " [\n", - " \"Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.\",\n", - " \"Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.\"\n", - " ]\n", - " ],\n", - " \"items\": {\n", - " \"$id\": \"#/properties/Method/items\",\n", - " \"anyOf\": [\n", - " {\n", - " \"$id\": \"#/properties/Method/items/anyOf/0\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.\",\n", - " \"Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.\"\n", - " ],\n", - " \"title\": \"The first anyOf schema\",\n", - " \"type\": \"string\"\n", - " }\n", - " ]\n", - " },\n", - " \"title\": \"The Method schema\",\n", - " \"type\": \"array\"\n", - " },\n", - " \"Name\": {\n", - " \"$id\": \"#/properties/Name\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"Christmas pie\"\n", - " ],\n", - " \"title\": \"The Name schema\",\n", - " \"type\": \"string\"\n", - " },\n", - " \"url\": {\n", - " \"$id\": \"#/properties/url\",\n", - " \"default\": \"\",\n", - " \"description\": \"An explanation about the purpose of this instance.\",\n", - " \"examples\": [\n", - " \"https://www.bbcgoodfood.com/recipes/2793/christmas-pie\"\n", - " ],\n", - " \"title\": \"The url schema\",\n", - " \"type\": \"string\"\n", - " }\n", - " },\n", - " \"required\": [\n", - " \"Name\",\n", - " \"url\",\n", - " \"Description\",\n", - " \"Author\",\n", - " \"Ingredients\",\n", - " \"Method\"\n", - " ],\n", - " \"title\": \"The root schema\",\n", - " \"type\": \"object\"\n", - " },\n", - " \"name\": \"one_recipe\"\n", - " }\n", - " ]\n", - "}\n", - "--------------------\n", - "From catalog ../sample_files/catalog_github.json:\n", - "--------------------\n", - "{\n", - " \"streams\": [\n", - " {\n", - " \"json_schema\": {\n", - " \"additionalProperties\": false,\n", - " \"properties\": {\n", - " \"_sdc_repository\": {\n", - " \"type\": [\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"comments_url\": {\n", - " \"description\": \"The URL to the commit's comments page\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"commit\": {\n", - " \"additionalProperties\": false,\n", - " \"properties\": {\n", - " \"author\": {\n", - " \"additionalProperties\": false,\n", - " \"properties\": {\n", - " \"date\": {\n", - " \"description\": \"The date the author committed the change\",\n", - " \"format\": \"date-time\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"email\": {\n", - " \"description\": \"The author's email\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"login\": {\n", - " \"description\": \"The author's login\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"name\": {\n", - " \"description\": \"The author's name\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"comment_count\": {\n", - " \"description\": \"The number of comments on the commit\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"committer\": {\n", - " \"additionalProperties\": false,\n", - " \"properties\": {\n", - " \"date\": {\n", - " \"description\": \"The date the committer committed the change\",\n", - " \"format\": \"date-time\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"email\": {\n", - " \"description\": \"The committer's email\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"login\": {\n", - " \"description\": \"The committer's login\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"name\": {\n", - " \"description\": \"The committer's name\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"message\": {\n", - " \"description\": \"The commit message\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"tree\": {\n", - " \"additionalProperties\": false,\n", - " \"properties\": {\n", - " \"sha\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"url\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"url\": {\n", - " \"description\": \"The URL to the commit\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"files\": {\n", - " \"items\": {\n", - " \"properties\": {\n", - " \"additions\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"number\"\n", - " ]\n", - " },\n", - " \"blob_url\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"changes\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"number\"\n", - " ]\n", - " },\n", - " \"deletions\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"number\"\n", - " ]\n", - " },\n", - " \"filename\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"patch\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"raw_url\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"status\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"array\"\n", - " ]\n", - " },\n", - " \"html_url\": {\n", - " \"description\": \"The HTML URL to the commit\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"id\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"parents\": {\n", - " \"items\": {\n", - " \"additionalProperties\": false,\n", - " \"properties\": {\n", - " \"html_url\": {\n", - " \"description\": \"The HTML URL to the parent commit\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"sha\": {\n", - " \"description\": \"The git hash of the parent commit\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"url\": {\n", - " \"description\": \"The URL to the parent commit\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"array\"\n", - " ]\n", - " },\n", - " \"pr_id\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"pr_number\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"sha\": {\n", - " \"description\": \"The git commit hash\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"url\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"name\": \"commits\"\n", - " }\n", - " ]\n", - "}\n", - "--------------------\n", - "From catalog ../sample_files/catalog_stripe.json:\n", - "--------------------\n", - "{\n", - " \"streams\": [\n", - " {\n", - " \"name\": \"customers\",\n", - " \"schema\": {\n", - " \"properties\": {\n", - " \"account_balance\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"cards\": {\n", - " \"items\": {\n", - " \"properties\": {\n", - " \"address_city\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_country\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_line1\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_line1_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_line2\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_state\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_zip\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_zip_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"brand\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"country\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"customer\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"cvc_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"dynamic_last4\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"exp_month\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"exp_year\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"fingerprint\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"funding\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"id\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"last4\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"metadata\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"object\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"tokenization_method\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"type\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"array\"\n", - " ]\n", - " },\n", - " \"created\": {\n", - " \"format\": \"date-time\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"currency\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"default_card\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"default_source\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"delinquent\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"boolean\"\n", - " ]\n", - " },\n", - " \"description\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"discount\": {\n", - " \"properties\": {\n", - " \"coupon\": {\n", - " \"properties\": {\n", - " \"amount_off\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"created\": {\n", - " \"format\": \"date-time\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"currency\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"duration\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"duration_in_months\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"id\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"livemode\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"boolean\"\n", - " ]\n", - " },\n", - " \"max_redemptions\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"metadata\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"object\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"percent_off\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"percent_off_precise\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"number\"\n", - " ]\n", - " },\n", - " \"redeem_by\": {\n", - " \"format\": \"date-time\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"times_redeemed\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"valid\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"boolean\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"customer\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"end\": {\n", - " \"format\": \"date-time\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"object\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"start\": {\n", - " \"format\": \"date-time\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"subscription\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"email\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"id\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"invoice_prefix\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"livemode\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"boolean\"\n", - " ]\n", - " },\n", - " \"metadata\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"object\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"shipping\": {\n", - " \"properties\": {\n", - " \"address\": {\n", - " \"properties\": {\n", - " \"city\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"country\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"line1\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"line2\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"postal_code\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"state\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"phone\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"sources\": {\n", - " \"anyOf\": [\n", - " {\n", - " \"items\": {\n", - " \"properties\": {\n", - " \"ach_credit_transfer\": {\n", - " \"properties\": {\n", - " \"account_number\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"bank_name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"fingerprint\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"refund_account_holder_name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"refund_account_holder_type\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"refund_account_number\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"refund_routing_number\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"routing_number\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"swift_code\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"address_city\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_country\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_line1\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_line1_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_line2\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_state\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_zip\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_zip_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"alipay\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"amount\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"bancontact\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"brand\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"card\": {\n", - " \"properties\": {\n", - " \"address_line1_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_zip_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"brand\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"country\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"cvc_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"dynamic_last4\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"exp_month\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"exp_year\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"fingerprint\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"funding\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"last4\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"three_d_secure\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"tokenization_method\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"type\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"client_secret\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"country\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"created\": {\n", - " \"format\": \"date-time\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"currency\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"customer\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"cvc_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"dynamic_last4\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"eps\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"exp_month\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"exp_year\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"fingerprint\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"flow\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"funding\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"id\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"ideal\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"last4\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"livemode\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"boolean\"\n", - " ]\n", - " },\n", - " \"metadata\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"multibanco\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"object\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"owner\": {\n", - " \"properties\": {\n", - " \"address\": {\n", - " \"properties\": {\n", - " \"city\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"country\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"line1\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"line2\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"postal_code\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"state\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"email\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"phone\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"verified_address\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"verified_email\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"verified_name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"verified_phone\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"receiver\": {\n", - " \"properties\": {\n", - " \"address\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"amount_charged\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"amount_received\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"amount_returned\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"refund_attributes_method\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"refund_attributes_status\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"redirect\": {\n", - " \"properties\": {\n", - " \"failure_reason\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"return_url\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"status\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"url\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"statement_descriptor\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"status\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"tokenization_method\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"type\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"usage\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"array\"\n", - " ]\n", - " },\n", - " {\n", - " \"properties\": {\n", - " \"ach_credit_transfer\": {\n", - " \"properties\": {\n", - " \"account_number\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"bank_name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"fingerprint\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"refund_account_holder_name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"refund_account_holder_type\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"refund_account_number\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"refund_routing_number\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"routing_number\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"swift_code\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"address_city\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_country\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_line1\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_line1_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_line2\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_state\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_zip\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_zip_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"alipay\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"amount\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"bancontact\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"brand\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"card\": {\n", - " \"properties\": {\n", - " \"address_line1_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"address_zip_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"brand\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"country\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"cvc_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"dynamic_last4\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"exp_month\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"exp_year\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"fingerprint\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"funding\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"last4\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"three_d_secure\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"tokenization_method\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"type\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"client_secret\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"country\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"created\": {\n", - " \"format\": \"date-time\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"currency\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"customer\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"cvc_check\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"dynamic_last4\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"eps\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"exp_month\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"exp_year\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"fingerprint\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"flow\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"funding\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"id\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"ideal\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"last4\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"livemode\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"boolean\"\n", - " ]\n", - " },\n", - " \"metadata\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"multibanco\": {\n", - " \"properties\": {},\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"object\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"owner\": {\n", - " \"properties\": {\n", - " \"address\": {\n", - " \"properties\": {\n", - " \"city\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"country\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"line1\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"line2\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"postal_code\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"state\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"email\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"phone\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"verified_address\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"verified_email\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"verified_name\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"verified_phone\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"receiver\": {\n", - " \"properties\": {\n", - " \"address\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"amount_charged\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"amount_received\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"amount_returned\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"integer\"\n", - " ]\n", - " },\n", - " \"refund_attributes_method\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"refund_attributes_status\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"redirect\": {\n", - " \"properties\": {\n", - " \"failure_reason\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"return_url\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"status\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"url\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " },\n", - " \"statement_descriptor\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"status\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"tokenization_method\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"type\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"usage\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " }\n", - " ]\n", - " },\n", - " \"subscriptions\": {\n", - " \"items\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"array\"\n", - " ]\n", - " },\n", - " \"tax_info\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"tax_info_verification\": {\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " },\n", - " \"updated\": {\n", - " \"format\": \"date-time\",\n", - " \"type\": [\n", - " \"null\",\n", - " \"string\"\n", - " ]\n", - " }\n", - " },\n", - " \"type\": [\n", - " \"null\",\n", - " \"object\"\n", - " ]\n", - " }\n", - " }\n", - " ]\n", - "}\n", - "--------------------\n" - ] - } - ], - "source": [ - "def load_catalog(file):\n", - " with open(file) as f:\n", - " catalog = json.load(f)\n", - " print(f\"From catalog {file}:\")\n", - " print(\"--------------------\")\n", - " print(json.dumps(catalog, sort_keys=True, indent=4))\n", - " print(\"--------------------\")\n", - " return catalog\n", - "\n", - "catalog = load_catalog(\"../sample_files/catalog.json\")\n", - "github = load_catalog(\"../sample_files/catalog_github.json\")\n", - "stripe = load_catalog(\"../sample_files/catalog_stripe.json\")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "def is_string(property_type) -> bool:\n", - " return property_type == \"string\" or \"string\" in property_type\n", - "\n", - "def is_integer(property_type) -> bool:\n", - " return property_type == \"integer\" or \"integer\" in property_type\n", - "\n", - "def is_boolean(property_type) -> bool:\n", - " return property_type == \"boolean\" or \"boolean\" in property_type\n", - " \n", - "def is_array(property_type) -> bool:\n", - " return property_type == \"array\" or \"array\" in property_type\n", - "\n", - "def is_object(property_type) -> bool:\n", - " return property_type == \"object\" or \"object\" in property_type\n", - "\n", - "def find_combining_schema(properties: dict):\n", - " return set(properties).intersection(set([\"anyOf\", \"oneOf\", \"allOf\"]))\n", - " \n", - "def json_extract_base_property(path: str, json_col: str, name: str, definition: dict) -> str:\n", - " current = \".\".join([path, name])\n", - " if not \"type\" in definition:\n", - " return None\n", - " elif is_string(definition[\"type\"]):\n", - " return f\"cast(json_extract_scalar({json_col}, '{current}') as string) as {name}\"\n", - " elif is_integer(definition[\"type\"]):\n", - " return f\"cast(json_extract_scalar({json_col}, '{current}') as int64) as {name}\"\n", - " elif is_boolean(definition[\"type\"]):\n", - " return f\"cast(json_extract_scalar({json_col}, '{current}') as boolean) as {name}\"\n", - " else:\n", - " return None\n", - " \n", - "def json_extract_nested_property(path: str, json_col: str, name: str, definition: dict) -> str:\n", - " current = \".\".join([path, name]) \n", - " if definition == None or not \"type\" in definition:\n", - " return (None, None)\n", - " elif is_array(definition[\"type\"]):\n", - " return (\n", - " f\"json_extract_array({json_col}, '{current}') as {name}\",\n", - " f\"cross join unnest({name}) as {name}\"\n", - " )\n", - " elif is_object(definition[\"type\"]):\n", - " return (\n", - " f\"json_extract({json_col}, '{current}') as {name}\",\n", - " \"\"\n", - " )\n", - " else:\n", - " return (None, None)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In File one_recipe.sql:\n", - "--------------------\n", - "with \n", - "one_recipe_node as (\n", - " select \n", - " \n", - " cast(json_extract_scalar(json_blob, '$.Name') as string) as Name,\n", - " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", - " cast(json_extract_scalar(json_blob, '$.Description') as string) as Description,\n", - " cast(json_extract_scalar(json_blob, '$.Author') as string) as Author\n", - " from `airbytesandbox.data.one_recipe_json`\n", - "),\n", - "one_recipe_with_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(Name as string), \"\"),\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(Description as string), \"\"),\n", - " coalesce(cast(Author as string), \"\")\n", - " ))) as _one_recipe_hashid\n", - " from one_recipe_node\n", - ")\n", - "select * from one_recipe_with_id\n", - "--------------------\n", - "In File one_recipe_Ingredients.sql:\n", - "--------------------\n", - "with \n", - "one_recipe_node as (\n", - " select \n", - " json_extract_array(json_blob, '$.Ingredients') as Ingredients,\n", - " cast(json_extract_scalar(json_blob, '$.Name') as string) as Name,\n", - " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", - " cast(json_extract_scalar(json_blob, '$.Description') as string) as Description,\n", - " cast(json_extract_scalar(json_blob, '$.Author') as string) as Author\n", - " from `airbytesandbox.data.one_recipe_json`\n", - "),\n", - "one_recipe_with_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(Name as string), \"\"),\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(Description as string), \"\"),\n", - " coalesce(cast(Author as string), \"\")\n", - " ))) as _one_recipe_hashid,\n", - " Ingredients\n", - " from one_recipe_node\n", - " cross join unnest(Ingredients) as Ingredients\n", - "),\n", - "one_recipe_Ingredients_node as (\n", - " select \n", - " _one_recipe_hashid as _one_recipe_foreign_hashid,\n", - " cast(json_extract_scalar(Ingredients, '$.quantity') as string) as quantity,\n", - " cast(json_extract_scalar(Ingredients, '$.ingredient') as string) as ingredient\n", - " from one_recipe_with_id\n", - "),\n", - "one_recipe_Ingredients_with_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(quantity as string), \"\"),\n", - " coalesce(cast(ingredient as string), \"\")\n", - " ))) as _one_recipe_Ingredients_hashid\n", - " from one_recipe_Ingredients_node\n", - ")\n", - "select * from one_recipe_Ingredients_with_id\n", - "--------------------\n", - "In File one_recipe_Ingredients_nutritionfacts.sql:\n", - "--------------------\n", - "with \n", - "one_recipe_node as (\n", - " select \n", - " json_extract_array(json_blob, '$.Ingredients') as Ingredients,\n", - " cast(json_extract_scalar(json_blob, '$.Name') as string) as Name,\n", - " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", - " cast(json_extract_scalar(json_blob, '$.Description') as string) as Description,\n", - " cast(json_extract_scalar(json_blob, '$.Author') as string) as Author\n", - " from `airbytesandbox.data.one_recipe_json`\n", - "),\n", - "one_recipe_with_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(Name as string), \"\"),\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(Description as string), \"\"),\n", - " coalesce(cast(Author as string), \"\")\n", - " ))) as _one_recipe_hashid,\n", - " Ingredients\n", - " from one_recipe_node\n", - " cross join unnest(Ingredients) as Ingredients\n", - "),\n", - "one_recipe_Ingredients_node as (\n", - " select \n", - " json_extract(Ingredients, '$.nutritionfacts') as nutritionfacts,\n", - " cast(json_extract_scalar(Ingredients, '$.quantity') as string) as quantity,\n", - " cast(json_extract_scalar(Ingredients, '$.ingredient') as string) as ingredient\n", - " from one_recipe_with_id\n", - "),\n", - "one_recipe_Ingredients_with_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(quantity as string), \"\"),\n", - " coalesce(cast(ingredient as string), \"\")\n", - " ))) as _one_recipe_Ingredients_hashid,\n", - " nutritionfacts\n", - " from one_recipe_Ingredients_node\n", - " \n", - "),\n", - "one_recipe_Ingredients_nutritionfacts_node as (\n", - " select \n", - " _one_recipe_Ingredients_hashid as _one_recipe_Ingredients_foreign_hashid,\n", - " cast(json_extract_scalar(nutritionfacts, '$.calories') as int64) as calories,\n", - " cast(json_extract_scalar(nutritionfacts, '$.fat') as string) as fat\n", - " from one_recipe_Ingredients_with_id\n", - "),\n", - "one_recipe_Ingredients_nutritionfacts_with_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(calories as string), \"\"),\n", - " coalesce(cast(fat as string), \"\")\n", - " ))) as _one_recipe_Ingredients_nutritionfacts_hashid\n", - " from one_recipe_Ingredients_nutritionfacts_node\n", - ")\n", - "select * from one_recipe_Ingredients_nutritionfacts_with_id\n", - "--------------------\n", - "In File one_recipe_Method.sql:\n", - "--------------------\n", - "with \n", - "one_recipe_node as (\n", - " select \n", - " json_extract_array(json_blob, '$.Method') as Method,\n", - " cast(json_extract_scalar(json_blob, '$.Name') as string) as Name,\n", - " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", - " cast(json_extract_scalar(json_blob, '$.Description') as string) as Description,\n", - " cast(json_extract_scalar(json_blob, '$.Author') as string) as Author\n", - " from `airbytesandbox.data.one_recipe_json`\n", - "),\n", - "one_recipe_with_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(Name as string), \"\"),\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(Description as string), \"\"),\n", - " coalesce(cast(Author as string), \"\")\n", - " ))) as _one_recipe_hashid,\n", - " Method\n", - " from one_recipe_node\n", - " cross join unnest(Method) as Method\n", - ")\n", - "select \n", - " _one_recipe_hashid as _one_recipe_foreign_hashid,\n", - " Method\n", - " from one_recipe_with_id\n", - "--------------------\n" - ] - } - ], - "source": [ - "def select_table(table: str, columns=\"*\"):\n", - " return f\"\\nselect {columns} from {table}\"\n", - "\n", - "def extract_node_properties(path: str, json_col: str, properties: dict) -> dict:\n", - " result = {}\n", - " if properties:\n", - " for field in properties.keys():\n", - " sql_field = json_extract_base_property(path=path, json_col=json_col, name=field, definition=properties[field])\n", - " if sql_field:\n", - " result[field] = sql_field \n", - " return result\n", - "\n", - "def find_properties_object(path: str, field: str, properties) -> dict: \n", - " if isinstance(properties, str) or isinstance(properties, int):\n", - " return None \n", - " else: \n", - " if \"items\" in properties:\n", - " return find_properties_object(path, field, properties[\"items\"])\n", - " elif \"properties\" in properties: \n", - " # we found a properties object\n", - " return {field: properties['properties']}\n", - " elif \"type\" in properties and json_extract_base_property(path=path, json_col=\"\", name=\"\", definition=properties): \n", - " # we found a basic type \n", - " return {field: None}\n", - " elif isinstance(properties, dict): \n", - " for key in properties.keys():\n", - " if not json_extract_base_property(path, \"\", key, properties[key]):\n", - " child = find_properties_object(path, key, properties[key])\n", - " if child:\n", - " return child\n", - " elif isinstance(properties, list): \n", - " for item in properties:\n", - " child = find_properties_object(path=path, field=field, properties=item)\n", - " if child:\n", - " return child \n", - " return None\n", - " \n", - "def extract_nested_properties(path: str, json_col: str, field: str, properties: dict) -> dict:\n", - " result = {}\n", - " if properties: \n", - " for key in properties.keys():\n", - " combining = find_combining_schema(properties[key])\n", - " if combining: \n", - " # skip combining schemas\n", - " for combo in combining:\n", - " found = find_properties_object(path=f\"{path}.{field}.{key}\", field=key, properties=properties[key][combo]) \n", - " result.update(found) \n", - " elif not \"type\" in properties[key]: \n", - " pass\n", - " elif is_array(properties[key]['type']): \n", - " combining = find_combining_schema(properties[key]['items'])\n", - " if combining:\n", - " # skip combining schemas\n", - " for combo in combining:\n", - " found = find_properties_object(path=f\"{path}.{key}\", field=key, properties=properties[key]['items'][combo])\n", - " result.update(found) \n", - " else:\n", - " found = find_properties_object(path=f\"{path}.{key}\", field=key, properties=properties[key]['items']) \n", - " result.update(found) \n", - " elif is_object(properties[key]['type']): \n", - " found = find_properties_object(path=f\"{path}.{key}\", field=key, properties=properties[key]) \n", - " result.update(found) \n", - " return result\n", - "\n", - "def process_node(path: str, json_col: str, name: str, properties: dict, from_table: str = \"\", previous=\"with \", inject_cols=\"\") -> dict:\n", - " result = {}\n", - " if previous == \"with \":\n", - " prefix = previous\n", - " else:\n", - " prefix = previous + \",\"\n", - " node_properties = extract_node_properties(path=path, json_col=json_col, properties=properties)\n", - " node_columns = ',\\n '.join([sql for sql in node_properties.values()])\n", - " # FIXME: use DBT macros to be cross_db compatible instead\n", - " hash_node_columns = 'coalesce(cast(' + ' as string), \"\"),\\n coalesce(cast('.join([column for column in node_properties.keys()]) + ' as string), \"\")' \n", - " node_sql = f\"\"\"{prefix}\n", - "{name}_node as (\n", - " select \n", - " {inject_cols}\n", - " {node_columns}\n", - " from {from_table}\n", - "),\n", - "{name}_with_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " {hash_node_columns}\n", - " ))) as _{name}_hashid\n", - " from {name}_node\n", - ")\"\"\"\n", - " # SQL Query for current node's basic properties\n", - " result[name] = node_sql + select_table(f\"{name}_with_id\")\n", - "\n", - " children_columns = extract_nested_properties(path=path, json_col=json_col, field=name, properties=properties)\n", - " if children_columns: \n", - " for col in children_columns.keys(): \n", - " child_col, join_child_table = json_extract_nested_property(path=path, json_col=json_col, name=col, definition=properties[col])\n", - " child_sql = f\"\"\"{prefix}\n", - "{name}_node as (\n", - " select \n", - " {child_col},\n", - " {node_columns}\n", - " from {from_table}\n", - "),\n", - "{name}_with_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " {hash_node_columns}\n", - " ))) as _{name}_hashid,\n", - " {col}\n", - " from {name}_node\n", - " {join_child_table}\n", - ")\"\"\"\n", - " if children_columns[col]:\n", - " children = process_node(path=\"$\", json_col=col, name=f\"{name}_{col}\", properties=children_columns[col], from_table=f\"{name}_with_id\", previous=child_sql, inject_cols=f\"_{name}_hashid as _{name}_foreign_hashid,\")\n", - " result.update(children)\n", - " else:\n", - " # SQL Query for current node's basic properties\n", - " result[f\"{name}_{col}\"] = child_sql + select_table(f\"{name}_with_id\", columns=f\"\"\"\n", - " _{name}_hashid as _{name}_foreign_hashid,\n", - " {col}\n", - "\"\"\")\n", - " return result\n", - "\n", - "def generate_dbt_model(catalog: dict, json_col: str, from_table: str) -> dict:\n", - " result = {}\n", - " for obj in catalog[\"streams\"]:\n", - " name = obj['name']\n", - " if \"json_schema\" in obj:\n", - " properties = obj['json_schema']['properties']\n", - " elif \"schema\" in obj:\n", - " properties = obj['schema']['properties']\n", - " result.update(process_node(path=\"$\", json_col=json_col, name=name, properties=properties, from_table=from_table))\n", - " return result\n", - "\n", - "def print_result(result):\n", - " for name in result.keys():\n", - " print(f\"In File {name}.sql:\")\n", - " print(\"--------------------\")\n", - " print(result[name])\n", - " print(\"--------------------\")\n", - "\n", - "print_result(generate_dbt_model(catalog=catalog, json_col=\"json_blob\", from_table=\"`airbytesandbox.data.one_recipe_json`\"))" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In File customers.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " \n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid\n", - " from customers_node\n", - ")\n", - "select * from customers_id\n", - "--------------------\n", - "In File customers_metadata.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " json_extract(json_blob, '$.metadata') as metadata,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " metadata\n", - " from customers_node\n", - " \n", - ")\n", - "select \n", - " _customers_hashid as _customers_foreign_hashid,\n", - " metadata\n", - " from customers_id\n", - "--------------------\n", - "In File customers_shipping.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " json_extract(json_blob, '$.shipping') as shipping,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " shipping\n", - " from customers_node\n", - " \n", - "),\n", - "customers_shipping_node as (\n", - " select \n", - " _customers_hashid as _customers_foreign_hashid,\n", - " cast(json_extract_scalar(shipping, '$.name') as string) as name,\n", - " cast(json_extract_scalar(shipping, '$.phone') as string) as phone\n", - " from customers_id\n", - "),\n", - "customers_shipping_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(phone as string), \"\")\n", - " ))) as _customers_shipping_hashid\n", - " from customers_shipping_node\n", - ")\n", - "select * from customers_shipping_id\n", - "--------------------\n", - "In File customers_shipping_address.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " json_extract(json_blob, '$.shipping') as shipping,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " shipping\n", - " from customers_node\n", - " \n", - "),\n", - "customers_shipping_node as (\n", - " select \n", - " json_extract(shipping, '$.address') as address,\n", - " cast(json_extract_scalar(shipping, '$.name') as string) as name,\n", - " cast(json_extract_scalar(shipping, '$.phone') as string) as phone\n", - " from customers_id\n", - "),\n", - "customers_shipping_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(phone as string), \"\")\n", - " ))) as _customers_shipping_hashid,\n", - " address\n", - " from customers_shipping_node\n", - " \n", - "),\n", - "customers_shipping_address_node as (\n", - " select \n", - " _customers_shipping_hashid as _customers_shipping_foreign_hashid,\n", - " cast(json_extract_scalar(address, '$.line2') as string) as line2,\n", - " cast(json_extract_scalar(address, '$.state') as string) as state,\n", - " cast(json_extract_scalar(address, '$.city') as string) as city,\n", - " cast(json_extract_scalar(address, '$.postal_code') as string) as postal_code,\n", - " cast(json_extract_scalar(address, '$.country') as string) as country,\n", - " cast(json_extract_scalar(address, '$.line1') as string) as line1\n", - " from customers_shipping_id\n", - "),\n", - "customers_shipping_address_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(line2 as string), \"\"),\n", - " coalesce(cast(state as string), \"\"),\n", - " coalesce(cast(city as string), \"\"),\n", - " coalesce(cast(postal_code as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(line1 as string), \"\")\n", - " ))) as _customers_shipping_address_hashid\n", - " from customers_shipping_address_node\n", - ")\n", - "select * from customers_shipping_address_id\n", - "--------------------\n", - "In File customers_sources.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " _customers_hashid as _customers_foreign_hashid,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid\n", - " from customers_sources_node\n", - ")\n", - "select * from customers_sources_id\n", - "--------------------\n", - "In File customers_sources_metadata.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " json_extract(sources, '$.metadata') as metadata,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid,\n", - " metadata\n", - " from customers_sources_node\n", - " \n", - ")\n", - "select \n", - " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", - " metadata\n", - " from customers_sources_id\n", - "--------------------\n", - "In File customers_sources_card.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " json_extract(sources, '$.card') as card,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid,\n", - " card\n", - " from customers_sources_node\n", - " \n", - "),\n", - "customers_sources_card_node as (\n", - " select \n", - " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", - " cast(json_extract_scalar(card, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(card, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(card, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(card, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(card, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(card, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(card, '$.name') as string) as name,\n", - " cast(json_extract_scalar(card, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(card, '$.three_d_secure') as string) as three_d_secure,\n", - " cast(json_extract_scalar(card, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(card, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(card, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(card, '$.country') as string) as country,\n", - " cast(json_extract_scalar(card, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(card, '$.type') as string) as type\n", - " from customers_sources_id\n", - "),\n", - "customers_sources_card_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(three_d_secure as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(type as string), \"\")\n", - " ))) as _customers_sources_card_hashid\n", - " from customers_sources_card_node\n", - ")\n", - "select * from customers_sources_card_id\n", - "--------------------\n", - "In File customers_sources_owner.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " json_extract(sources, '$.owner') as owner,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid,\n", - " owner\n", - " from customers_sources_node\n", - " \n", - "),\n", - "customers_sources_owner_node as (\n", - " select \n", - " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", - " cast(json_extract_scalar(owner, '$.verified_address') as string) as verified_address,\n", - " cast(json_extract_scalar(owner, '$.email') as string) as email,\n", - " cast(json_extract_scalar(owner, '$.verified_email') as string) as verified_email,\n", - " cast(json_extract_scalar(owner, '$.name') as string) as name,\n", - " cast(json_extract_scalar(owner, '$.phone') as string) as phone,\n", - " cast(json_extract_scalar(owner, '$.verified_name') as string) as verified_name,\n", - " cast(json_extract_scalar(owner, '$.verified_phone') as string) as verified_phone\n", - " from customers_sources_id\n", - "),\n", - "customers_sources_owner_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(verified_address as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(verified_email as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(phone as string), \"\"),\n", - " coalesce(cast(verified_name as string), \"\"),\n", - " coalesce(cast(verified_phone as string), \"\")\n", - " ))) as _customers_sources_owner_hashid\n", - " from customers_sources_owner_node\n", - ")\n", - "select * from customers_sources_owner_id\n", - "--------------------\n", - "In File customers_sources_owner_address.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " json_extract(sources, '$.owner') as owner,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid,\n", - " owner\n", - " from customers_sources_node\n", - " \n", - "),\n", - "customers_sources_owner_node as (\n", - " select \n", - " json_extract(owner, '$.address') as address,\n", - " cast(json_extract_scalar(owner, '$.verified_address') as string) as verified_address,\n", - " cast(json_extract_scalar(owner, '$.email') as string) as email,\n", - " cast(json_extract_scalar(owner, '$.verified_email') as string) as verified_email,\n", - " cast(json_extract_scalar(owner, '$.name') as string) as name,\n", - " cast(json_extract_scalar(owner, '$.phone') as string) as phone,\n", - " cast(json_extract_scalar(owner, '$.verified_name') as string) as verified_name,\n", - " cast(json_extract_scalar(owner, '$.verified_phone') as string) as verified_phone\n", - " from customers_sources_id\n", - "),\n", - "customers_sources_owner_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(verified_address as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(verified_email as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(phone as string), \"\"),\n", - " coalesce(cast(verified_name as string), \"\"),\n", - " coalesce(cast(verified_phone as string), \"\")\n", - " ))) as _customers_sources_owner_hashid,\n", - " address\n", - " from customers_sources_owner_node\n", - " \n", - "),\n", - "customers_sources_owner_address_node as (\n", - " select \n", - " _customers_sources_owner_hashid as _customers_sources_owner_foreign_hashid,\n", - " cast(json_extract_scalar(address, '$.line2') as string) as line2,\n", - " cast(json_extract_scalar(address, '$.state') as string) as state,\n", - " cast(json_extract_scalar(address, '$.city') as string) as city,\n", - " cast(json_extract_scalar(address, '$.postal_code') as string) as postal_code,\n", - " cast(json_extract_scalar(address, '$.country') as string) as country,\n", - " cast(json_extract_scalar(address, '$.line1') as string) as line1\n", - " from customers_sources_owner_id\n", - "),\n", - "customers_sources_owner_address_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(line2 as string), \"\"),\n", - " coalesce(cast(state as string), \"\"),\n", - " coalesce(cast(city as string), \"\"),\n", - " coalesce(cast(postal_code as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(line1 as string), \"\")\n", - " ))) as _customers_sources_owner_address_hashid\n", - " from customers_sources_owner_address_node\n", - ")\n", - "select * from customers_sources_owner_address_id\n", - "--------------------\n", - "In File customers_sources_receiver.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " json_extract(sources, '$.receiver') as receiver,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid,\n", - " receiver\n", - " from customers_sources_node\n", - " \n", - "),\n", - "customers_sources_receiver_node as (\n", - " select \n", - " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", - " cast(json_extract_scalar(receiver, '$.refund_attributes_method') as string) as refund_attributes_method,\n", - " cast(json_extract_scalar(receiver, '$.amount_returned') as int64) as amount_returned,\n", - " cast(json_extract_scalar(receiver, '$.amount_received') as int64) as amount_received,\n", - " cast(json_extract_scalar(receiver, '$.refund_attributes_status') as string) as refund_attributes_status,\n", - " cast(json_extract_scalar(receiver, '$.address') as string) as address,\n", - " cast(json_extract_scalar(receiver, '$.amount_charged') as int64) as amount_charged\n", - " from customers_sources_id\n", - "),\n", - "customers_sources_receiver_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(refund_attributes_method as string), \"\"),\n", - " coalesce(cast(amount_returned as string), \"\"),\n", - " coalesce(cast(amount_received as string), \"\"),\n", - " coalesce(cast(refund_attributes_status as string), \"\"),\n", - " coalesce(cast(address as string), \"\"),\n", - " coalesce(cast(amount_charged as string), \"\")\n", - " ))) as _customers_sources_receiver_hashid\n", - " from customers_sources_receiver_node\n", - ")\n", - "select * from customers_sources_receiver_id\n", - "--------------------\n", - "In File customers_sources_ach_credit_transfer.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " json_extract(sources, '$.ach_credit_transfer') as ach_credit_transfer,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid,\n", - " ach_credit_transfer\n", - " from customers_sources_node\n", - " \n", - "),\n", - "customers_sources_ach_credit_transfer_node as (\n", - " select \n", - " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", - " cast(json_extract_scalar(ach_credit_transfer, '$.bank_name') as string) as bank_name,\n", - " cast(json_extract_scalar(ach_credit_transfer, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(ach_credit_transfer, '$.routing_number') as string) as routing_number,\n", - " cast(json_extract_scalar(ach_credit_transfer, '$.swift_code') as string) as swift_code,\n", - " cast(json_extract_scalar(ach_credit_transfer, '$.refund_account_holder_type') as string) as refund_account_holder_type,\n", - " cast(json_extract_scalar(ach_credit_transfer, '$.refund_account_holder_name') as string) as refund_account_holder_name,\n", - " cast(json_extract_scalar(ach_credit_transfer, '$.refund_account_number') as string) as refund_account_number,\n", - " cast(json_extract_scalar(ach_credit_transfer, '$.refund_routing_number') as string) as refund_routing_number,\n", - " cast(json_extract_scalar(ach_credit_transfer, '$.account_number') as string) as account_number\n", - " from customers_sources_id\n", - "),\n", - "customers_sources_ach_credit_transfer_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(bank_name as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(routing_number as string), \"\"),\n", - " coalesce(cast(swift_code as string), \"\"),\n", - " coalesce(cast(refund_account_holder_type as string), \"\"),\n", - " coalesce(cast(refund_account_holder_name as string), \"\"),\n", - " coalesce(cast(refund_account_number as string), \"\"),\n", - " coalesce(cast(refund_routing_number as string), \"\"),\n", - " coalesce(cast(account_number as string), \"\")\n", - " ))) as _customers_sources_ach_credit_transfer_hashid\n", - " from customers_sources_ach_credit_transfer_node\n", - ")\n", - "select * from customers_sources_ach_credit_transfer_id\n", - "--------------------\n", - "In File customers_sources_alipay.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " json_extract(sources, '$.alipay') as alipay,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid,\n", - " alipay\n", - " from customers_sources_node\n", - " \n", - ")\n", - "select \n", - " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", - " alipay\n", - " from customers_sources_id\n", - "--------------------\n", - "In File customers_sources_bancontact.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " json_extract(sources, '$.bancontact') as bancontact,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid,\n", - " bancontact\n", - " from customers_sources_node\n", - " \n", - ")\n", - "select \n", - " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", - " bancontact\n", - " from customers_sources_id\n", - "--------------------\n", - "In File customers_sources_eps.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " json_extract(sources, '$.eps') as eps,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid,\n", - " eps\n", - " from customers_sources_node\n", - " \n", - ")\n", - "select \n", - " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", - " eps\n", - " from customers_sources_id\n", - "--------------------\n", - "In File customers_sources_ideal.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " json_extract(sources, '$.ideal') as ideal,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid,\n", - " ideal\n", - " from customers_sources_node\n", - " \n", - ")\n", - "select \n", - " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", - " ideal\n", - " from customers_sources_id\n", - "--------------------\n", - "In File customers_sources_multibanco.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " json_extract(sources, '$.multibanco') as multibanco,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid,\n", - " multibanco\n", - " from customers_sources_node\n", - " \n", - ")\n", - "select \n", - " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", - " multibanco\n", - " from customers_sources_id\n", - "--------------------\n", - "In File customers_sources_redirect.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " None,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " sources\n", - " from customers_node\n", - " None\n", - "),\n", - "customers_sources_node as (\n", - " select \n", - " json_extract(sources, '$.redirect') as redirect,\n", - " cast(json_extract_scalar(sources, '$.type') as string) as type,\n", - " cast(json_extract_scalar(sources, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(sources, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(sources, '$.statement_descriptor') as string) as statement_descriptor,\n", - " cast(json_extract_scalar(sources, '$.id') as string) as id,\n", - " cast(json_extract_scalar(sources, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(sources, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(sources, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(sources, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(sources, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(sources, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(sources, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(sources, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(sources, '$.country') as string) as country,\n", - " cast(json_extract_scalar(sources, '$.object') as string) as object,\n", - " cast(json_extract_scalar(sources, '$.amount') as int64) as amount,\n", - " cast(json_extract_scalar(sources, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(sources, '$.usage') as string) as usage,\n", - " cast(json_extract_scalar(sources, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(sources, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(sources, '$.client_secret') as string) as client_secret,\n", - " cast(json_extract_scalar(sources, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(sources, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(sources, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(sources, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(sources, '$.flow') as string) as flow,\n", - " cast(json_extract_scalar(sources, '$.name') as string) as name,\n", - " cast(json_extract_scalar(sources, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(sources, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(sources, '$.status') as string) as status,\n", - " cast(json_extract_scalar(sources, '$.created') as string) as created,\n", - " cast(json_extract_scalar(sources, '$.address_state') as string) as address_state\n", - " from customers_id\n", - "),\n", - "customers_sources_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(type as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(statement_descriptor as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(amount as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(usage as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(client_secret as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(flow as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\")\n", - " ))) as _customers_sources_hashid,\n", - " redirect\n", - " from customers_sources_node\n", - " \n", - "),\n", - "customers_sources_redirect_node as (\n", - " select \n", - " _customers_sources_hashid as _customers_sources_foreign_hashid,\n", - " cast(json_extract_scalar(redirect, '$.failure_reason') as string) as failure_reason,\n", - " cast(json_extract_scalar(redirect, '$.return_url') as string) as return_url,\n", - " cast(json_extract_scalar(redirect, '$.status') as string) as status,\n", - " cast(json_extract_scalar(redirect, '$.url') as string) as url\n", - " from customers_sources_id\n", - "),\n", - "customers_sources_redirect_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(failure_reason as string), \"\"),\n", - " coalesce(cast(return_url as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(url as string), \"\")\n", - " ))) as _customers_sources_redirect_hashid\n", - " from customers_sources_redirect_node\n", - ")\n", - "select * from customers_sources_redirect_id\n", - "--------------------\n", - "In File customers_cards.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " json_extract_array(json_blob, '$.cards') as cards,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " cards\n", - " from customers_node\n", - " cross join unnest(cards) as cards\n", - "),\n", - "customers_cards_node as (\n", - " select \n", - " _customers_hashid as _customers_foreign_hashid,\n", - " cast(json_extract_scalar(cards, '$.object') as string) as object,\n", - " cast(json_extract_scalar(cards, '$.id') as string) as id,\n", - " cast(json_extract_scalar(cards, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(cards, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(cards, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(cards, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(cards, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(cards, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(cards, '$.country') as string) as country,\n", - " cast(json_extract_scalar(cards, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(cards, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(cards, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(cards, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(cards, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(cards, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(cards, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(cards, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(cards, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(cards, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(cards, '$.name') as string) as name,\n", - " cast(json_extract_scalar(cards, '$.address_state') as string) as address_state,\n", - " cast(json_extract_scalar(cards, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(cards, '$.type') as string) as type\n", - " from customers_id\n", - "),\n", - "customers_cards_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(type as string), \"\")\n", - " ))) as _customers_cards_hashid\n", - " from customers_cards_node\n", - ")\n", - "select * from customers_cards_id\n", - "--------------------\n", - "In File customers_cards_metadata.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " json_extract_array(json_blob, '$.cards') as cards,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " cards\n", - " from customers_node\n", - " cross join unnest(cards) as cards\n", - "),\n", - "customers_cards_node as (\n", - " select \n", - " json_extract(cards, '$.metadata') as metadata,\n", - " cast(json_extract_scalar(cards, '$.object') as string) as object,\n", - " cast(json_extract_scalar(cards, '$.id') as string) as id,\n", - " cast(json_extract_scalar(cards, '$.exp_month') as int64) as exp_month,\n", - " cast(json_extract_scalar(cards, '$.dynamic_last4') as string) as dynamic_last4,\n", - " cast(json_extract_scalar(cards, '$.exp_year') as int64) as exp_year,\n", - " cast(json_extract_scalar(cards, '$.last4') as string) as last4,\n", - " cast(json_extract_scalar(cards, '$.funding') as string) as funding,\n", - " cast(json_extract_scalar(cards, '$.brand') as string) as brand,\n", - " cast(json_extract_scalar(cards, '$.country') as string) as country,\n", - " cast(json_extract_scalar(cards, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(cards, '$.cvc_check') as string) as cvc_check,\n", - " cast(json_extract_scalar(cards, '$.address_line2') as string) as address_line2,\n", - " cast(json_extract_scalar(cards, '$.address_line1') as string) as address_line1,\n", - " cast(json_extract_scalar(cards, '$.fingerprint') as string) as fingerprint,\n", - " cast(json_extract_scalar(cards, '$.address_zip') as string) as address_zip,\n", - " cast(json_extract_scalar(cards, '$.address_city') as string) as address_city,\n", - " cast(json_extract_scalar(cards, '$.address_country') as string) as address_country,\n", - " cast(json_extract_scalar(cards, '$.address_line1_check') as string) as address_line1_check,\n", - " cast(json_extract_scalar(cards, '$.tokenization_method') as string) as tokenization_method,\n", - " cast(json_extract_scalar(cards, '$.name') as string) as name,\n", - " cast(json_extract_scalar(cards, '$.address_state') as string) as address_state,\n", - " cast(json_extract_scalar(cards, '$.address_zip_check') as string) as address_zip_check,\n", - " cast(json_extract_scalar(cards, '$.type') as string) as type\n", - " from customers_id\n", - "),\n", - "customers_cards_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(exp_month as string), \"\"),\n", - " coalesce(cast(dynamic_last4 as string), \"\"),\n", - " coalesce(cast(exp_year as string), \"\"),\n", - " coalesce(cast(last4 as string), \"\"),\n", - " coalesce(cast(funding as string), \"\"),\n", - " coalesce(cast(brand as string), \"\"),\n", - " coalesce(cast(country as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(cvc_check as string), \"\"),\n", - " coalesce(cast(address_line2 as string), \"\"),\n", - " coalesce(cast(address_line1 as string), \"\"),\n", - " coalesce(cast(fingerprint as string), \"\"),\n", - " coalesce(cast(address_zip as string), \"\"),\n", - " coalesce(cast(address_city as string), \"\"),\n", - " coalesce(cast(address_country as string), \"\"),\n", - " coalesce(cast(address_line1_check as string), \"\"),\n", - " coalesce(cast(tokenization_method as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(address_state as string), \"\"),\n", - " coalesce(cast(address_zip_check as string), \"\"),\n", - " coalesce(cast(type as string), \"\")\n", - " ))) as _customers_cards_hashid,\n", - " metadata\n", - " from customers_cards_node\n", - " \n", - ")\n", - "select \n", - " _customers_cards_hashid as _customers_cards_foreign_hashid,\n", - " metadata\n", - " from customers_cards_id\n", - "--------------------\n", - "In File customers_subscriptions.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " json_extract_array(json_blob, '$.subscriptions') as subscriptions,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " subscriptions\n", - " from customers_node\n", - " cross join unnest(subscriptions) as subscriptions\n", - ")\n", - "select \n", - " _customers_hashid as _customers_foreign_hashid,\n", - " subscriptions\n", - " from customers_id\n", - "--------------------\n", - "In File customers_discount.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " json_extract(json_blob, '$.discount') as discount,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " discount\n", - " from customers_node\n", - " \n", - "),\n", - "customers_discount_node as (\n", - " select \n", - " _customers_hashid as _customers_foreign_hashid,\n", - " cast(json_extract_scalar(discount, '$.end') as string) as end,\n", - " cast(json_extract_scalar(discount, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(discount, '$.start') as string) as start,\n", - " cast(json_extract_scalar(discount, '$.object') as string) as object,\n", - " cast(json_extract_scalar(discount, '$.subscription') as string) as subscription\n", - " from customers_id\n", - "),\n", - "customers_discount_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(end as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(start as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(subscription as string), \"\")\n", - " ))) as _customers_discount_hashid\n", - " from customers_discount_node\n", - ")\n", - "select * from customers_discount_id\n", - "--------------------\n", - "In File customers_discount_coupon.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " json_extract(json_blob, '$.discount') as discount,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " discount\n", - " from customers_node\n", - " \n", - "),\n", - "customers_discount_node as (\n", - " select \n", - " json_extract(discount, '$.coupon') as coupon,\n", - " cast(json_extract_scalar(discount, '$.end') as string) as end,\n", - " cast(json_extract_scalar(discount, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(discount, '$.start') as string) as start,\n", - " cast(json_extract_scalar(discount, '$.object') as string) as object,\n", - " cast(json_extract_scalar(discount, '$.subscription') as string) as subscription\n", - " from customers_id\n", - "),\n", - "customers_discount_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(end as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(start as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(subscription as string), \"\")\n", - " ))) as _customers_discount_hashid,\n", - " coupon\n", - " from customers_discount_node\n", - " \n", - "),\n", - "customers_discount_coupon_node as (\n", - " select \n", - " _customers_discount_hashid as _customers_discount_foreign_hashid,\n", - " cast(json_extract_scalar(coupon, '$.valid') as boolean) as valid,\n", - " cast(json_extract_scalar(coupon, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(coupon, '$.amount_off') as int64) as amount_off,\n", - " cast(json_extract_scalar(coupon, '$.redeem_by') as string) as redeem_by,\n", - " cast(json_extract_scalar(coupon, '$.duration_in_months') as int64) as duration_in_months,\n", - " cast(json_extract_scalar(coupon, '$.max_redemptions') as int64) as max_redemptions,\n", - " cast(json_extract_scalar(coupon, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(coupon, '$.name') as string) as name,\n", - " cast(json_extract_scalar(coupon, '$.times_redeemed') as int64) as times_redeemed,\n", - " cast(json_extract_scalar(coupon, '$.id') as string) as id,\n", - " cast(json_extract_scalar(coupon, '$.duration') as string) as duration,\n", - " cast(json_extract_scalar(coupon, '$.object') as string) as object,\n", - " cast(json_extract_scalar(coupon, '$.percent_off') as int64) as percent_off,\n", - " cast(json_extract_scalar(coupon, '$.created') as string) as created\n", - " from customers_discount_id\n", - "),\n", - "customers_discount_coupon_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(valid as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(amount_off as string), \"\"),\n", - " coalesce(cast(redeem_by as string), \"\"),\n", - " coalesce(cast(duration_in_months as string), \"\"),\n", - " coalesce(cast(max_redemptions as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(times_redeemed as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(duration as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(percent_off as string), \"\"),\n", - " coalesce(cast(created as string), \"\")\n", - " ))) as _customers_discount_coupon_hashid\n", - " from customers_discount_coupon_node\n", - ")\n", - "select * from customers_discount_coupon_id\n", - "--------------------\n", - "In File customers_discount_coupon_metadata.sql:\n", - "--------------------\n", - "with \n", - "customers_node as (\n", - " select \n", - " json_extract(json_blob, '$.discount') as discount,\n", - " cast(json_extract_scalar(json_blob, '$.delinquent') as boolean) as delinquent,\n", - " cast(json_extract_scalar(json_blob, '$.description') as string) as description,\n", - " cast(json_extract_scalar(json_blob, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(json_blob, '$.default_source') as string) as default_source,\n", - " cast(json_extract_scalar(json_blob, '$.email') as string) as email,\n", - " cast(json_extract_scalar(json_blob, '$.default_card') as string) as default_card,\n", - " cast(json_extract_scalar(json_blob, '$.account_balance') as int64) as account_balance,\n", - " cast(json_extract_scalar(json_blob, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id,\n", - " cast(json_extract_scalar(json_blob, '$.invoice_prefix') as string) as invoice_prefix,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info_verification') as string) as tax_info_verification,\n", - " cast(json_extract_scalar(json_blob, '$.object') as string) as object,\n", - " cast(json_extract_scalar(json_blob, '$.created') as string) as created,\n", - " cast(json_extract_scalar(json_blob, '$.tax_info') as string) as tax_info,\n", - " cast(json_extract_scalar(json_blob, '$.updated') as string) as updated\n", - " from `airbytesandbox.data.stripe_json`\n", - "),\n", - "customers_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(delinquent as string), \"\"),\n", - " coalesce(cast(description as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(default_source as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(default_card as string), \"\"),\n", - " coalesce(cast(account_balance as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(invoice_prefix as string), \"\"),\n", - " coalesce(cast(tax_info_verification as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(created as string), \"\"),\n", - " coalesce(cast(tax_info as string), \"\"),\n", - " coalesce(cast(updated as string), \"\")\n", - " ))) as _customers_hashid,\n", - " discount\n", - " from customers_node\n", - " \n", - "),\n", - "customers_discount_node as (\n", - " select \n", - " json_extract(discount, '$.coupon') as coupon,\n", - " cast(json_extract_scalar(discount, '$.end') as string) as end,\n", - " cast(json_extract_scalar(discount, '$.customer') as string) as customer,\n", - " cast(json_extract_scalar(discount, '$.start') as string) as start,\n", - " cast(json_extract_scalar(discount, '$.object') as string) as object,\n", - " cast(json_extract_scalar(discount, '$.subscription') as string) as subscription\n", - " from customers_id\n", - "),\n", - "customers_discount_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(end as string), \"\"),\n", - " coalesce(cast(customer as string), \"\"),\n", - " coalesce(cast(start as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(subscription as string), \"\")\n", - " ))) as _customers_discount_hashid,\n", - " coupon\n", - " from customers_discount_node\n", - " \n", - "),\n", - "customers_discount_coupon_node as (\n", - " select \n", - " json_extract(coupon, '$.metadata') as metadata,\n", - " cast(json_extract_scalar(coupon, '$.valid') as boolean) as valid,\n", - " cast(json_extract_scalar(coupon, '$.livemode') as boolean) as livemode,\n", - " cast(json_extract_scalar(coupon, '$.amount_off') as int64) as amount_off,\n", - " cast(json_extract_scalar(coupon, '$.redeem_by') as string) as redeem_by,\n", - " cast(json_extract_scalar(coupon, '$.duration_in_months') as int64) as duration_in_months,\n", - " cast(json_extract_scalar(coupon, '$.max_redemptions') as int64) as max_redemptions,\n", - " cast(json_extract_scalar(coupon, '$.currency') as string) as currency,\n", - " cast(json_extract_scalar(coupon, '$.name') as string) as name,\n", - " cast(json_extract_scalar(coupon, '$.times_redeemed') as int64) as times_redeemed,\n", - " cast(json_extract_scalar(coupon, '$.id') as string) as id,\n", - " cast(json_extract_scalar(coupon, '$.duration') as string) as duration,\n", - " cast(json_extract_scalar(coupon, '$.object') as string) as object,\n", - " cast(json_extract_scalar(coupon, '$.percent_off') as int64) as percent_off,\n", - " cast(json_extract_scalar(coupon, '$.created') as string) as created\n", - " from customers_discount_id\n", - "),\n", - "customers_discount_coupon_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(valid as string), \"\"),\n", - " coalesce(cast(livemode as string), \"\"),\n", - " coalesce(cast(amount_off as string), \"\"),\n", - " coalesce(cast(redeem_by as string), \"\"),\n", - " coalesce(cast(duration_in_months as string), \"\"),\n", - " coalesce(cast(max_redemptions as string), \"\"),\n", - " coalesce(cast(currency as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(times_redeemed as string), \"\"),\n", - " coalesce(cast(id as string), \"\"),\n", - " coalesce(cast(duration as string), \"\"),\n", - " coalesce(cast(object as string), \"\"),\n", - " coalesce(cast(percent_off as string), \"\"),\n", - " coalesce(cast(created as string), \"\")\n", - " ))) as _customers_discount_coupon_hashid,\n", - " metadata\n", - " from customers_discount_coupon_node\n", - " \n", - ")\n", - "select \n", - " _customers_discount_coupon_hashid as _customers_discount_coupon_foreign_hashid,\n", - " metadata\n", - " from customers_discount_coupon_id\n", - "--------------------\n" - ] - } - ], - "source": [ - "print_result(generate_dbt_model(catalog=stripe, json_col=\"json_blob\", from_table=\"`airbytesandbox.data.stripe_json`\"))" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "In File commits.sql:\n", - "--------------------\n", - "with \n", - "commits_node as (\n", - " select \n", - " \n", - " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", - " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", - " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", - " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", - " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", - " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", - " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", - " from `airbytesandbox.data.github_json`\n", - "),\n", - "commits_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(_sdc_repository as string), \"\"),\n", - " coalesce(cast(sha as string), \"\"),\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(html_url as string), \"\"),\n", - " coalesce(cast(comments_url as string), \"\"),\n", - " coalesce(cast(pr_number as string), \"\"),\n", - " coalesce(cast(pr_id as string), \"\"),\n", - " coalesce(cast(id as string), \"\")\n", - " ))) as _commits_hashid\n", - " from commits_node\n", - ")\n", - "select * from commits_id\n", - "--------------------\n", - "In File commits_parents.sql:\n", - "--------------------\n", - "with \n", - "commits_node as (\n", - " select \n", - " json_extract_array(json_blob, '$.parents') as parents,\n", - " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", - " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", - " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", - " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", - " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", - " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", - " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", - " from `airbytesandbox.data.github_json`\n", - "),\n", - "commits_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(_sdc_repository as string), \"\"),\n", - " coalesce(cast(sha as string), \"\"),\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(html_url as string), \"\"),\n", - " coalesce(cast(comments_url as string), \"\"),\n", - " coalesce(cast(pr_number as string), \"\"),\n", - " coalesce(cast(pr_id as string), \"\"),\n", - " coalesce(cast(id as string), \"\")\n", - " ))) as _commits_hashid,\n", - " parents\n", - " from commits_node\n", - " cross join unnest(parents) as parents\n", - "),\n", - "commits_parents_node as (\n", - " select \n", - " _commits_hashid as _commits_foreign_hashid,\n", - " cast(json_extract_scalar(parents, '$.sha') as string) as sha,\n", - " cast(json_extract_scalar(parents, '$.url') as string) as url,\n", - " cast(json_extract_scalar(parents, '$.html_url') as string) as html_url\n", - " from commits_id\n", - "),\n", - "commits_parents_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(sha as string), \"\"),\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(html_url as string), \"\")\n", - " ))) as _commits_parents_hashid\n", - " from commits_parents_node\n", - ")\n", - "select * from commits_parents_id\n", - "--------------------\n", - "In File commits_files.sql:\n", - "--------------------\n", - "with \n", - "commits_node as (\n", - " select \n", - " json_extract_array(json_blob, '$.files') as files,\n", - " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", - " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", - " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", - " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", - " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", - " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", - " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", - " from `airbytesandbox.data.github_json`\n", - "),\n", - "commits_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(_sdc_repository as string), \"\"),\n", - " coalesce(cast(sha as string), \"\"),\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(html_url as string), \"\"),\n", - " coalesce(cast(comments_url as string), \"\"),\n", - " coalesce(cast(pr_number as string), \"\"),\n", - " coalesce(cast(pr_id as string), \"\"),\n", - " coalesce(cast(id as string), \"\")\n", - " ))) as _commits_hashid,\n", - " files\n", - " from commits_node\n", - " cross join unnest(files) as files\n", - "),\n", - "commits_files_node as (\n", - " select \n", - " _commits_hashid as _commits_foreign_hashid,\n", - " cast(json_extract_scalar(files, '$.filename') as string) as filename,\n", - " cast(json_extract_scalar(files, '$.status') as string) as status,\n", - " cast(json_extract_scalar(files, '$.raw_url') as string) as raw_url,\n", - " cast(json_extract_scalar(files, '$.blob_url') as string) as blob_url,\n", - " cast(json_extract_scalar(files, '$.patch') as string) as patch\n", - " from commits_id\n", - "),\n", - "commits_files_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(filename as string), \"\"),\n", - " coalesce(cast(status as string), \"\"),\n", - " coalesce(cast(raw_url as string), \"\"),\n", - " coalesce(cast(blob_url as string), \"\"),\n", - " coalesce(cast(patch as string), \"\")\n", - " ))) as _commits_files_hashid\n", - " from commits_files_node\n", - ")\n", - "select * from commits_files_id\n", - "--------------------\n", - "In File commits_commit.sql:\n", - "--------------------\n", - "with \n", - "commits_node as (\n", - " select \n", - " json_extract(json_blob, '$.commit') as commit,\n", - " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", - " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", - " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", - " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", - " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", - " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", - " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", - " from `airbytesandbox.data.github_json`\n", - "),\n", - "commits_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(_sdc_repository as string), \"\"),\n", - " coalesce(cast(sha as string), \"\"),\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(html_url as string), \"\"),\n", - " coalesce(cast(comments_url as string), \"\"),\n", - " coalesce(cast(pr_number as string), \"\"),\n", - " coalesce(cast(pr_id as string), \"\"),\n", - " coalesce(cast(id as string), \"\")\n", - " ))) as _commits_hashid,\n", - " commit\n", - " from commits_node\n", - " \n", - "),\n", - "commits_commit_node as (\n", - " select \n", - " _commits_hashid as _commits_foreign_hashid,\n", - " cast(json_extract_scalar(commit, '$.url') as string) as url,\n", - " cast(json_extract_scalar(commit, '$.message') as string) as message,\n", - " cast(json_extract_scalar(commit, '$.comment_count') as int64) as comment_count\n", - " from commits_id\n", - "),\n", - "commits_commit_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(message as string), \"\"),\n", - " coalesce(cast(comment_count as string), \"\")\n", - " ))) as _commits_commit_hashid\n", - " from commits_commit_node\n", - ")\n", - "select * from commits_commit_id\n", - "--------------------\n", - "In File commits_commit_tree.sql:\n", - "--------------------\n", - "with \n", - "commits_node as (\n", - " select \n", - " json_extract(json_blob, '$.commit') as commit,\n", - " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", - " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", - " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", - " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", - " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", - " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", - " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", - " from `airbytesandbox.data.github_json`\n", - "),\n", - "commits_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(_sdc_repository as string), \"\"),\n", - " coalesce(cast(sha as string), \"\"),\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(html_url as string), \"\"),\n", - " coalesce(cast(comments_url as string), \"\"),\n", - " coalesce(cast(pr_number as string), \"\"),\n", - " coalesce(cast(pr_id as string), \"\"),\n", - " coalesce(cast(id as string), \"\")\n", - " ))) as _commits_hashid,\n", - " commit\n", - " from commits_node\n", - " \n", - "),\n", - "commits_commit_node as (\n", - " select \n", - " json_extract(commit, '$.tree') as tree,\n", - " cast(json_extract_scalar(commit, '$.url') as string) as url,\n", - " cast(json_extract_scalar(commit, '$.message') as string) as message,\n", - " cast(json_extract_scalar(commit, '$.comment_count') as int64) as comment_count\n", - " from commits_id\n", - "),\n", - "commits_commit_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(message as string), \"\"),\n", - " coalesce(cast(comment_count as string), \"\")\n", - " ))) as _commits_commit_hashid,\n", - " tree\n", - " from commits_commit_node\n", - " \n", - "),\n", - "commits_commit_tree_node as (\n", - " select \n", - " _commits_commit_hashid as _commits_commit_foreign_hashid,\n", - " cast(json_extract_scalar(tree, '$.sha') as string) as sha,\n", - " cast(json_extract_scalar(tree, '$.url') as string) as url\n", - " from commits_commit_id\n", - "),\n", - "commits_commit_tree_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(sha as string), \"\"),\n", - " coalesce(cast(url as string), \"\")\n", - " ))) as _commits_commit_tree_hashid\n", - " from commits_commit_tree_node\n", - ")\n", - "select * from commits_commit_tree_id\n", - "--------------------\n", - "In File commits_commit_author.sql:\n", - "--------------------\n", - "with \n", - "commits_node as (\n", - " select \n", - " json_extract(json_blob, '$.commit') as commit,\n", - " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", - " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", - " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", - " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", - " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", - " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", - " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", - " from `airbytesandbox.data.github_json`\n", - "),\n", - "commits_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(_sdc_repository as string), \"\"),\n", - " coalesce(cast(sha as string), \"\"),\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(html_url as string), \"\"),\n", - " coalesce(cast(comments_url as string), \"\"),\n", - " coalesce(cast(pr_number as string), \"\"),\n", - " coalesce(cast(pr_id as string), \"\"),\n", - " coalesce(cast(id as string), \"\")\n", - " ))) as _commits_hashid,\n", - " commit\n", - " from commits_node\n", - " \n", - "),\n", - "commits_commit_node as (\n", - " select \n", - " json_extract(commit, '$.author') as author,\n", - " cast(json_extract_scalar(commit, '$.url') as string) as url,\n", - " cast(json_extract_scalar(commit, '$.message') as string) as message,\n", - " cast(json_extract_scalar(commit, '$.comment_count') as int64) as comment_count\n", - " from commits_id\n", - "),\n", - "commits_commit_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(message as string), \"\"),\n", - " coalesce(cast(comment_count as string), \"\")\n", - " ))) as _commits_commit_hashid,\n", - " author\n", - " from commits_commit_node\n", - " \n", - "),\n", - "commits_commit_author_node as (\n", - " select \n", - " _commits_commit_hashid as _commits_commit_foreign_hashid,\n", - " cast(json_extract_scalar(author, '$.date') as string) as date,\n", - " cast(json_extract_scalar(author, '$.name') as string) as name,\n", - " cast(json_extract_scalar(author, '$.email') as string) as email,\n", - " cast(json_extract_scalar(author, '$.login') as string) as login\n", - " from commits_commit_id\n", - "),\n", - "commits_commit_author_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(date as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(login as string), \"\")\n", - " ))) as _commits_commit_author_hashid\n", - " from commits_commit_author_node\n", - ")\n", - "select * from commits_commit_author_id\n", - "--------------------\n", - "In File commits_commit_committer.sql:\n", - "--------------------\n", - "with \n", - "commits_node as (\n", - " select \n", - " json_extract(json_blob, '$.commit') as commit,\n", - " cast(json_extract_scalar(json_blob, '$._sdc_repository') as string) as _sdc_repository,\n", - " cast(json_extract_scalar(json_blob, '$.sha') as string) as sha,\n", - " cast(json_extract_scalar(json_blob, '$.url') as string) as url,\n", - " cast(json_extract_scalar(json_blob, '$.html_url') as string) as html_url,\n", - " cast(json_extract_scalar(json_blob, '$.comments_url') as string) as comments_url,\n", - " cast(json_extract_scalar(json_blob, '$.pr_number') as int64) as pr_number,\n", - " cast(json_extract_scalar(json_blob, '$.pr_id') as string) as pr_id,\n", - " cast(json_extract_scalar(json_blob, '$.id') as string) as id\n", - " from `airbytesandbox.data.github_json`\n", - "),\n", - "commits_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(_sdc_repository as string), \"\"),\n", - " coalesce(cast(sha as string), \"\"),\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(html_url as string), \"\"),\n", - " coalesce(cast(comments_url as string), \"\"),\n", - " coalesce(cast(pr_number as string), \"\"),\n", - " coalesce(cast(pr_id as string), \"\"),\n", - " coalesce(cast(id as string), \"\")\n", - " ))) as _commits_hashid,\n", - " commit\n", - " from commits_node\n", - " \n", - "),\n", - "commits_commit_node as (\n", - " select \n", - " json_extract(commit, '$.committer') as committer,\n", - " cast(json_extract_scalar(commit, '$.url') as string) as url,\n", - " cast(json_extract_scalar(commit, '$.message') as string) as message,\n", - " cast(json_extract_scalar(commit, '$.comment_count') as int64) as comment_count\n", - " from commits_id\n", - "),\n", - "commits_commit_id as (\n", - " select\n", - " to_hex(md5(concat(\n", - " coalesce(cast(url as string), \"\"),\n", - " coalesce(cast(message as string), \"\"),\n", - " coalesce(cast(comment_count as string), \"\")\n", - " ))) as _commits_commit_hashid,\n", - " committer\n", - " from commits_commit_node\n", - " \n", - "),\n", - "commits_commit_committer_node as (\n", - " select \n", - " _commits_commit_hashid as _commits_commit_foreign_hashid,\n", - " cast(json_extract_scalar(committer, '$.date') as string) as date,\n", - " cast(json_extract_scalar(committer, '$.name') as string) as name,\n", - " cast(json_extract_scalar(committer, '$.email') as string) as email,\n", - " cast(json_extract_scalar(committer, '$.login') as string) as login\n", - " from commits_commit_id\n", - "),\n", - "commits_commit_committer_id as (\n", - " select\n", - " *,\n", - " to_hex(md5(concat(\n", - " coalesce(cast(date as string), \"\"),\n", - " coalesce(cast(name as string), \"\"),\n", - " coalesce(cast(email as string), \"\"),\n", - " coalesce(cast(login as string), \"\")\n", - " ))) as _commits_commit_committer_hashid\n", - " from commits_commit_committer_node\n", - ")\n", - "select * from commits_commit_committer_id\n", - "--------------------\n" - ] - } - ], - "source": [ - "print_result(generate_dbt_model(catalog=github, json_col=\"json_blob\", from_table=\"`airbytesandbox.data.github_json`\"))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.7" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.txt b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.txt deleted file mode 100644 index fb97e07e2dffd..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/normalization/normalization.txt +++ /dev/null @@ -1,264 +0,0 @@ -""" -MIT License - -Copyright (c) 2020 Airbyte - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -import json - - -# + -def load_catalog(file): - with open(file) as f: - catalog = json.load(f) - print(f"From catalog {file}:") - print("--------------------") - print(json.dumps(catalog, sort_keys=True, indent=4)) - print("--------------------") - return catalog - - -catalog = load_catalog("../sample_files/catalog.json") -github = load_catalog("../sample_files/catalog_github.json") -stripe = load_catalog("../sample_files/catalog_stripe.json") - - -# + -def is_string(property_type) -> bool: - return property_type == "string" or "string" in property_type - - -def is_integer(property_type) -> bool: - return property_type == "integer" or "integer" in property_type - - -def is_boolean(property_type) -> bool: - return property_type == "boolean" or "boolean" in property_type - - -def is_array(property_type) -> bool: - return property_type == "array" or "array" in property_type - - -def is_object(property_type) -> bool: - return property_type == "object" or "object" in property_type - - -def find_combining_schema(properties: dict): - return set(properties).intersection(set(["anyOf", "oneOf", "allOf"])) - - -def json_extract_base_property(path: str, json_col: str, name: str, definition: dict) -> str: - current = ".".join([path, name]) - if not "type" in definition: - return None - elif is_string(definition["type"]): - return f"cast(json_extract_scalar({json_col}, '{current}') as string) as {name}" - elif is_integer(definition["type"]): - return f"cast(json_extract_scalar({json_col}, '{current}') as int64) as {name}" - elif is_boolean(definition["type"]): - return f"cast(json_extract_scalar({json_col}, '{current}') as boolean) as {name}" - else: - return None - - -def json_extract_nested_property(path: str, json_col: str, name: str, definition: dict) -> str: - current = ".".join([path, name]) - if definition == None or not "type" in definition: - return (None, None) - elif is_array(definition["type"]): - return (f"json_extract_array({json_col}, '{current}') as {name}", f"cross join unnest({name}) as {name}") - elif is_object(definition["type"]): - return (f"json_extract({json_col}, '{current}') as {name}", "") - else: - return (None, None) - - -# + -def select_table(table: str, columns="*"): - return f"\nselect {columns} from {table}" - - -def extract_node_properties(path: str, json_col: str, properties: dict) -> dict: - result = {} - if properties: - for field in properties.keys(): - sql_field = json_extract_base_property(path=path, json_col=json_col, name=field, definition=properties[field]) - if sql_field: - result[field] = sql_field - return result - - -def find_properties_object(path: str, field: str, properties) -> dict: - if isinstance(properties, str) or isinstance(properties, int): - return None - else: - if "items" in properties: - return find_properties_object(path, field, properties["items"]) - elif "properties" in properties: - # we found a properties object - return {field: properties["properties"]} - elif "type" in properties and json_extract_base_property(path=path, json_col="", name="", definition=properties): - # we found a basic type - return {field: None} - elif isinstance(properties, dict): - for key in properties.keys(): - if not json_extract_base_property(path, "", key, properties[key]): - child = find_properties_object(path, key, properties[key]) - if child: - return child - elif isinstance(properties, list): - for item in properties: - child = find_properties_object(path=path, field=field, properties=item) - if child: - return child - return None - - -def extract_nested_properties(path: str, json_col: str, field: str, properties: dict) -> dict: - result = {} - if properties: - for key in properties.keys(): - combining = find_combining_schema(properties[key]) - if combining: - # skip combining schemas - for combo in combining: - found = find_properties_object(path=f"{path}.{field}.{key}", field=key, properties=properties[key][combo]) - result.update(found) - elif not "type" in properties[key]: - pass - elif is_array(properties[key]["type"]): - combining = find_combining_schema(properties[key]["items"]) - if combining: - # skip combining schemas - for combo in combining: - found = find_properties_object(path=f"{path}.{key}", field=key, properties=properties[key]["items"][combo]) - result.update(found) - else: - found = find_properties_object(path=f"{path}.{key}", field=key, properties=properties[key]["items"]) - result.update(found) - elif is_object(properties[key]["type"]): - found = find_properties_object(path=f"{path}.{key}", field=key, properties=properties[key]) - result.update(found) - return result - - -def process_node(path: str, json_col: str, name: str, properties: dict, from_table: str = "", previous="with ", inject_cols="") -> dict: - result = {} - if previous == "with ": - prefix = previous - else: - prefix = previous + "," - node_properties = extract_node_properties(path=path, json_col=json_col, properties=properties) - node_columns = ",\n ".join([sql for sql in node_properties.values()]) - # FIXME: use DBT macros to be cross_db compatible instead - hash_node_columns = ( - "coalesce(cast(" - + ' as string), ""),\n coalesce(cast('.join([column for column in node_properties.keys()]) - + ' as string), "")' - ) - node_sql = f"""{prefix} -{name}_node as ( - select - {inject_cols} - {node_columns} - from {from_table} -), -{name}_with_id as ( - select - *, - to_hex(md5(concat( - {hash_node_columns} - ))) as _{name}_hashid - from {name}_node -)""" - # SQL Query for current node's basic properties - result[name] = node_sql + select_table(f"{name}_with_id") - - children_columns = extract_nested_properties(path=path, json_col=json_col, field=name, properties=properties) - if children_columns: - for col in children_columns.keys(): - child_col, join_child_table = json_extract_nested_property(path=path, json_col=json_col, name=col, definition=properties[col]) - child_sql = f"""{prefix} -{name}_node as ( - select - {child_col}, - {node_columns} - from {from_table} -), -{name}_with_id as ( - select - to_hex(md5(concat( - {hash_node_columns} - ))) as _{name}_hashid, - {col} - from {name}_node - {join_child_table} -)""" - if children_columns[col]: - children = process_node( - path="$", - json_col=col, - name=f"{name}_{col}", - properties=children_columns[col], - from_table=f"{name}_with_id", - previous=child_sql, - inject_cols=f"_{name}_hashid as _{name}_foreign_hashid,", - ) - result.update(children) - else: - # SQL Query for current node's basic properties - result[f"{name}_{col}"] = child_sql + select_table( - f"{name}_with_id", - columns=f""" - _{name}_hashid as _{name}_foreign_hashid, - {col} -""", - ) - return result - - -def generate_dbt_model(catalog: dict, json_col: str, from_table: str) -> dict: - result = {} - for obj in catalog["streams"]: - name = obj["name"] - if "json_schema" in obj: - properties = obj["json_schema"]["properties"] - elif "schema" in obj: - properties = obj["schema"]["properties"] - result.update(process_node(path="$", json_col=json_col, name=name, properties=properties, from_table=from_table)) - return result - - -def print_result(result): - for name in result.keys(): - print(f"In File {name}.sql:") - print("--------------------") - print(result[name]) - print("--------------------") - - -print_result(generate_dbt_model(catalog=catalog, json_col="json_blob", from_table="`airbytesandbox.data.one_recipe_json`")) -# - - -print_result(generate_dbt_model(catalog=stripe, json_col="json_blob", from_table="`airbytesandbox.data.stripe_json`")) - -print_result(generate_dbt_model(catalog=github, json_col="json_blob", from_table="`airbytesandbox.data.github_json`")) diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog.json deleted file mode 100644 index 7cb3dd59836db..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog.json +++ /dev/null @@ -1,514 +0,0 @@ -{ - "streams": [ - { - "name": "one_recipe", - "json_schema": - { - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "http://example.com/example.json", - "type": "object", - "title": "The root schema", - "description": "The root schema comprises the entire JSON document.", - "default": {}, - "examples": [ - { - "Name": "Christmas pie", - "url": "https://www.bbcgoodfood.com/recipes/2793/christmas-pie", - "Description": "Combine a few key Christmas flavours here to make a pie that both children and adults will adore", - "Author": "Mary Cadogan", - "Ingredients": [ - { - "quantity": "2 tbsp", - "ingredient": "olive oil", - "nutritionfacts": { - "calories": 119, - "fat": "13.5g" - } - }, - { - "quantity": "knob", - "ingredient": "butter", - "nutritionfacts": { - "calories": 102, - "fat": "11.5g" - } - }, - { - "quantity": "1", - "ingredient": "onion", - "preparation": "finely chopped", - "nutritionfacts": { - "calories": 102, - "fat": "11.5g" - } - }, - { - "quantity": "500g", - "ingredient": "sausagemeat or skinned sausages", - "nutritionfacts": { - "calories": 268, - "fat": "18g" - } - }, - { - "quantity": "1", - "ingredient": "lemon", - "preparation": "grated zest", - "nutritionfacts": { - "calories": 13 - } - }, - { - "quantity": "100g", - "ingredient": "fresh white breadcrumbs" - }, - { - "quantity": "85g", - "ingredient": "ready-to-eat dried apricots", - "preparation": "chopped", - "nutritionfacts": { - "calories": 34, - "fat": "0.27g" - } - }, - { - "quantity": "58g", - "ingredient": "chestnut, canned or vacuum-packed", - "preparation": "chopped", - "nutritionfacts": { - "calories": 77, - "fat": "1g" - } - }, - { - "quantity": "2 tsp", - "ingredient": "fresh or dried thyme", - "preparation": "chopped" - }, - { - "quantity": "100g", - "ingredient": "cranberries, fresh or frozen" - }, - { - "quantity": "500g", - "ingredient": "boneless, skinless chicken breasts", - "nutritionfacts": { - "calories": 284, - "fat": "6.2g" - } - }, - { - "quantity": "500g", - "ingredient": "pack ready-made shortcrust pastry" - }, - { - "quantity": "1", - "ingredient": "beaten egg", - "preparation": "to glaze", - "nutritionfacts": { - "calories": 75, - "fat": "5g" - } - } - ], - "Method": [ - "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", - "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.", - "Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.", - "Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.", - "Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles." - ] - } - ], - "required": [ - "Name", - "url", - "Description", - "Author", - "Ingredients", - "Method" - ], - "properties": { - "Name": { - "$id": "#/properties/Name", - "type": "string", - "title": "The Name schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "Christmas pie" - ] - }, - "url": { - "$id": "#/properties/url", - "type": "string", - "title": "The url schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "https://www.bbcgoodfood.com/recipes/2793/christmas-pie" - ] - }, - "Description": { - "$id": "#/properties/Description", - "type": "string", - "title": "The Description schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "Combine a few key Christmas flavours here to make a pie that both children and adults will adore" - ] - }, - "Author": { - "$id": "#/properties/Author", - "type": "string", - "title": "The Author schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "Mary Cadogan" - ] - }, - "Ingredients": { - "$id": "#/properties/Ingredients", - "type": "array", - "title": "The Ingredients schema", - "description": "An explanation about the purpose of this instance.", - "default": [], - "examples": [ - [ - { - "quantity": "2 tbsp", - "ingredient": "olive oil", - "nutritionfacts": { - "calories": 119, - "fat": "13.5g" - } - }, - { - "quantity": "knob", - "ingredient": "butter", - "nutritionfacts": { - "calories": 102, - "fat": "11.5g" - } - } - ] - ], - "additionalItems": true, - "items": { - "$id": "#/properties/Ingredients/items", - "anyOf": [ - { - "$id": "#/properties/Ingredients/items/anyOf/0", - "type": "object", - "title": "The first anyOf schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "examples": [ - { - "quantity": "2 tbsp", - "ingredient": "olive oil", - "nutritionfacts": { - "calories": 119, - "fat": "13.5g" - } - } - ], - "required": [ - "quantity", - "ingredient", - "nutritionfacts" - ], - "properties": { - "quantity": { - "$id": "#/properties/Ingredients/items/anyOf/0/properties/quantity", - "type": "string", - "title": "The quantity schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "2 tbsp" - ] - }, - "ingredient": { - "$id": "#/properties/Ingredients/items/anyOf/0/properties/ingredient", - "type": "string", - "title": "The ingredient schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "olive oil" - ] - }, - "nutritionfacts": { - "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts", - "type": "object", - "title": "The nutritionfacts schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "examples": [ - { - "calories": 119, - "fat": "13.5g" - } - ], - "required": [ - "calories", - "fat" - ], - "properties": { - "calories": { - "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/calories", - "type": "integer", - "title": "The calories schema", - "description": "An explanation about the purpose of this instance.", - "default": 0, - "examples": [ - 119 - ] - }, - "fat": { - "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/fat", - "type": "string", - "title": "The fat schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "13.5g" - ] - } - }, - "additionalProperties": true - } - }, - "additionalProperties": true - }, - { - "$id": "#/properties/Ingredients/items/anyOf/1", - "type": "object", - "title": "The second anyOf schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "examples": [ - { - "quantity": "1", - "ingredient": "onion", - "preparation": "finely chopped", - "nutritionfacts": { - "calories": 102, - "fat": "11.5g" - } - } - ], - "required": [ - "quantity", - "ingredient", - "preparation", - "nutritionfacts" - ], - "properties": { - "quantity": { - "$id": "#/properties/Ingredients/items/anyOf/1/properties/quantity", - "type": "string", - "title": "The quantity schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "1" - ] - }, - "ingredient": { - "$id": "#/properties/Ingredients/items/anyOf/1/properties/ingredient", - "type": "string", - "title": "The ingredient schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "onion" - ] - }, - "preparation": { - "$id": "#/properties/Ingredients/items/anyOf/1/properties/preparation", - "type": "string", - "title": "The preparation schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "finely chopped" - ] - }, - "nutritionfacts": { - "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts", - "type": "object", - "title": "The nutritionfacts schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "examples": [ - { - "calories": 102, - "fat": "11.5g" - } - ], - "required": [ - "calories", - "fat" - ], - "properties": { - "calories": { - "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/calories", - "type": "integer", - "title": "The calories schema", - "description": "An explanation about the purpose of this instance.", - "default": 0, - "examples": [ - 102 - ] - }, - "fat": { - "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/fat", - "type": "string", - "title": "The fat schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "11.5g" - ] - } - }, - "additionalProperties": true - } - }, - "additionalProperties": true - }, - { - "$id": "#/properties/Ingredients/items/anyOf/2", - "type": "object", - "title": "The third anyOf schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "examples": [ - { - "quantity": "100g", - "ingredient": "fresh white breadcrumbs" - } - ], - "required": [ - "quantity", - "ingredient" - ], - "properties": { - "quantity": { - "$id": "#/properties/Ingredients/items/anyOf/2/properties/quantity", - "type": "string", - "title": "The quantity schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "100g" - ] - }, - "ingredient": { - "$id": "#/properties/Ingredients/items/anyOf/2/properties/ingredient", - "type": "string", - "title": "The ingredient schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "fresh white breadcrumbs" - ] - } - }, - "additionalProperties": true - }, - { - "$id": "#/properties/Ingredients/items/anyOf/3", - "type": "object", - "title": "The fourth anyOf schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "examples": [ - { - "quantity": "2 tsp", - "ingredient": "fresh or dried thyme", - "preparation": "chopped" - } - ], - "required": [ - "quantity", - "ingredient", - "preparation" - ], - "properties": { - "quantity": { - "$id": "#/properties/Ingredients/items/anyOf/3/properties/quantity", - "type": "string", - "title": "The quantity schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "2 tsp" - ] - }, - "ingredient": { - "$id": "#/properties/Ingredients/items/anyOf/3/properties/ingredient", - "type": "string", - "title": "The ingredient schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "fresh or dried thyme" - ] - }, - "preparation": { - "$id": "#/properties/Ingredients/items/anyOf/3/properties/preparation", - "type": "string", - "title": "The preparation schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "chopped" - ] - } - }, - "additionalProperties": true - } - ] - } - }, - "Method": { - "$id": "#/properties/Method", - "type": "array", - "title": "The Method schema", - "description": "An explanation about the purpose of this instance.", - "default": [], - "examples": [ - [ - "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", - "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins." - ] - ], - "additionalItems": true, - "items": { - "$id": "#/properties/Method/items", - "anyOf": [ - { - "$id": "#/properties/Method/items/anyOf/0", - "type": "string", - "title": "The first anyOf schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", - "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins." - ] - } - ] - } - } - }, - "additionalProperties": true - } - } - ] -} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_exchangerateapi.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_exchangerateapi.json deleted file mode 100644 index 36f42a5a028b4..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_exchangerateapi.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "streams": [ - { - "stream": "exchange_rate", - "schema": { - "type": "object", - "properties": { - "date": { "type": "string", "format": "date-time" }, - "CAD": { "type": ["null", "number"] }, - "HKD": { "type": ["null", "number"] }, - "ISK": { "type": ["null", "number"] }, - "PHP": { "type": ["null", "number"] }, - "DKK": { "type": ["null", "number"] }, - "HUF": { "type": ["null", "number"] }, - "CZK": { "type": ["null", "number"] }, - "GBP": { "type": ["null", "number"] }, - "RON": { "type": ["null", "number"] }, - "SEK": { "type": ["null", "number"] }, - "IDR": { "type": ["null", "number"] }, - "INR": { "type": ["null", "number"] }, - "BRL": { "type": ["null", "number"] }, - "RUB": { "type": ["null", "number"] }, - "HRK": { "type": ["null", "number"] }, - "JPY": { "type": ["null", "number"] }, - "THB": { "type": ["null", "number"] }, - "CHF": { "type": ["null", "number"] }, - "EUR": { "type": ["null", "number"] }, - "MYR": { "type": ["null", "number"] }, - "BGN": { "type": ["null", "number"] }, - "TRY": { "type": ["null", "number"] }, - "CNY": { "type": ["null", "number"] }, - "NOK": { "type": ["null", "number"] }, - "NZD": { "type": ["null", "number"] }, - "ZAR": { "type": ["null", "number"] }, - "USD": { "type": ["null", "number"] }, - "MXN": { "type": ["null", "number"] }, - "SGD": { "type": ["null", "number"] }, - "AUD": { "type": ["null", "number"] }, - "ILS": { "type": ["null", "number"] }, - "KRW": { "type": ["null", "number"] }, - "PLN": { "type": ["null", "number"] } - } - } - } - ] -} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_file.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_file.json deleted file mode 100644 index 9baccf44184c8..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_file.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "streams": [ - { - "name": "my_own_data_sample/my_file.csv", - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "date": { - "type": "string" - }, - "key": { - "type": "string" - }, - "total_confirmed": { - "type": "number" - }, - "total_healed": { - "type": "number" - }, - "total_deceased": { - "type": "number" - }, - "total_recovered": { - "type": "number" - }, - "total_tested": { - "type": "number" - } - } - } - } - ] -} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_github.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_github.json deleted file mode 100644 index 9b38d3536c2e3..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_github.json +++ /dev/null @@ -1,169 +0,0 @@ -{ - "streams": [ - { - "name": "commits", - "json_schema": { - "type": ["null", "object"], - "additionalProperties": false, - "properties": { - "_sdc_repository": { - "type": ["string"] - }, - "sha": { - "type": ["null", "string"], - "description": "The git commit hash" - }, - "url": { - "type": ["null", "string"] - }, - "parents": { - "type": ["null", "array"], - "items": { - "type": ["null", "object"], - "additionalProperties": false, - "properties": { - "sha": { - "type": ["null", "string"], - "description": "The git hash of the parent commit" - }, - "url": { - "type": ["null", "string"], - "description": "The URL to the parent commit" - }, - "html_url": { - "type": ["null", "string"], - "description": "The HTML URL to the parent commit" - } - } - } - }, - "files": { - "type": ["null", "array"], - "items": { - "type": ["null", "object"], - "properties": { - "filename": { - "type": ["null", "string"] - }, - "additions": { - "type": ["null", "number"] - }, - "deletions": { - "type": ["null", "number"] - }, - "changes": { - "type": ["null", "number"] - }, - "status": { - "type": ["null", "string"] - }, - "raw_url": { - "type": ["null", "string"] - }, - "blob_url": { - "type": ["null", "string"] - }, - "patch": { - "type": ["null", "string"] - } - } - } - }, - "html_url": { - "type": ["null", "string"], - "description": "The HTML URL to the commit" - }, - "comments_url": { - "type": ["null", "string"], - "description": "The URL to the commit's comments page" - }, - "commit": { - "type": ["null", "object"], - "additionalProperties": false, - "properties": { - "url": { - "type": ["null", "string"], - "description": "The URL to the commit" - }, - "tree": { - "type": ["null", "object"], - "additionalProperties": false, - "properties": { - "sha": { - "type": ["null", "string"] - }, - "url": { - "type": ["null", "string"] - } - } - }, - "author": { - "type": ["null", "object"], - "additionalProperties": false, - "properties": { - "date": { - "type": ["null", "string"], - "format": "date-time", - "description": "The date the author committed the change" - }, - "name": { - "type": ["null", "string"], - "description": "The author's name" - }, - "email": { - "type": ["null", "string"], - "description": "The author's email" - }, - "login": { - "type": ["null", "string"], - "description": "The author's login" - } - } - }, - "message": { - "type": ["null", "string"], - "description": "The commit message" - }, - "committer": { - "type": ["null", "object"], - "additionalProperties": false, - "properties": { - "date": { - "type": ["null", "string"], - "format": "date-time", - "description": "The date the committer committed the change" - }, - "name": { - "type": ["null", "string"], - "description": "The committer's name" - }, - "email": { - "type": ["null", "string"], - "description": "The committer's email" - }, - "login": { - "type": ["null", "string"], - "description": "The committer's login" - } - } - }, - "comment_count": { - "type": ["null", "integer"], - "description": "The number of comments on the commit" - } - } - }, - "pr_number": { - "type": ["null", "integer"] - }, - "pr_id": { - "type": ["null", "string"] - }, - "id": { - "type": ["null", "string"] - } - } - } - } - ] -} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_google-sheets.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_google-sheets.json deleted file mode 100644 index 438f0624af9e5..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_google-sheets.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "streams": [ - { - "name": "sheet1", - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "header1": { - "type": "string" - }, - "irrelevant": { - "type": "string" - }, - "header3": { - "type": "string" - }, - "ignored": { - "type": "string" - } - } - } - }, - { - "name": "sheet2", - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "header1": { - "type": "string" - }, - "irrelevant": { - "type": "string" - }, - "header3": { - "type": "string" - }, - "ignored": { - "type": "string" - } - } - } - } - ] -} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_hubspot.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_hubspot.json deleted file mode 100644 index f1e79f8e4bded..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_hubspot.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "streams": [ - { - "name": "owners", - "json_schema": { - "type": "object", - "properties": { - "portalId": { - "type": ["null", "integer"] - }, - "ownerId": { - "type": ["null", "integer"] - }, - "type": { - "type": ["null", "string"] - }, - "firstName": { - "type": ["null", "string"] - }, - "lastName": { - "type": ["null", "string"] - }, - "email": { - "type": ["null", "string"] - }, - "createdAt": { - "type": ["null", "string"], - "format": "date-time" - }, - "signature": { - "type": ["null", "string"] - }, - "updatedAt": { - "type": ["null", "string"], - "format": "date-time" - }, - "hasContactsAccess": { - "type": ["null", "boolean"] - }, - "isActive": { - "type": ["null", "boolean"] - }, - "activeUserId": { - "type": ["null", "integer"] - }, - "userIdIncludingInactive": { - "type": ["null", "integer"] - }, - "remoteList": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": ["null", "integer"] - }, - "portalId": { - "type": ["null", "integer"] - }, - "ownerId": { - "type": ["null", "integer"] - }, - "remoteId": { - "type": ["null", "string"] - }, - "remoteType": { - "type": ["null", "string"] - }, - "active": { - "type": ["null", "boolean"] - } - } - } - } - } - } - } - ] -} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_salesforce.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_salesforce.json deleted file mode 100644 index 85d1bca893957..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_salesforce.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "streams": [ - { - "name": "User", - "json_schema": { - "type": "object", - "additionalProperties": false, - "properties": { - "Id": { - "type": "string" - }, - "Username": { - "type": ["null", "string"] - }, - "Name": { - "type": ["null", "string"] - } - } - } - } - ] -} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_stripe.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_stripe.json deleted file mode 100644 index de802f60139cd..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/catalog_stripe.json +++ /dev/null @@ -1,863 +0,0 @@ -{ - "streams": [ - { - "name": "customers", - "schema": { - "type": ["null", "object"], - "properties": { - "metadata": { - "type": ["null", "object"], - "properties": {} - }, - "shipping": { - "type": ["null", "object"], - "properties": { - "address": { - "type": ["null", "object"], - "properties": { - "line2": { - "type": ["null", "string"] - }, - "state": { - "type": ["null", "string"] - }, - "city": { - "type": ["null", "string"] - }, - "postal_code": { - "type": ["null", "string"] - }, - "country": { - "type": ["null", "string"] - }, - "line1": { - "type": ["null", "string"] - } - } - }, - "name": { - "type": ["null", "string"] - }, - "phone": { - "type": ["null", "string"] - } - } - }, - "sources": { - "anyOf": [ - { - "type": ["null", "array"], - "items": { - "type": ["null", "object"], - "properties": { - "metadata": { - "type": ["null", "object"], - "properties": {} - }, - "type": { - "type": ["null", "string"] - }, - "address_zip": { - "type": ["null", "string"] - }, - "livemode": { - "type": ["null", "boolean"] - }, - "card": { - "type": ["null", "object"], - "properties": { - "fingerprint": { - "type": ["null", "string"] - }, - "last4": { - "type": ["null", "string"] - }, - "dynamic_last4": { - "type": ["null", "string"] - }, - "address_line1_check": { - "type": ["null", "string"] - }, - "exp_month": { - "type": ["null", "integer"] - }, - "tokenization_method": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "exp_year": { - "type": ["null", "integer"] - }, - "three_d_secure": { - "type": ["null", "string"] - }, - "funding": { - "type": ["null", "string"] - }, - "brand": { - "type": ["null", "string"] - }, - "cvc_check": { - "type": ["null", "string"] - }, - "country": { - "type": ["null", "string"] - }, - "address_zip_check": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "string"] - } - } - }, - "statement_descriptor": { - "type": ["null", "string"] - }, - "id": { - "type": ["null", "string"] - }, - "address_country": { - "type": ["null", "string"] - }, - "funding": { - "type": ["null", "string"] - }, - "dynamic_last4": { - "type": ["null", "string"] - }, - "exp_year": { - "type": ["null", "integer"] - }, - "last4": { - "type": ["null", "string"] - }, - "exp_month": { - "type": ["null", "integer"] - }, - "brand": { - "type": ["null", "string"] - }, - "address_line2": { - "type": ["null", "string"] - }, - "country": { - "type": ["null", "string"] - }, - "object": { - "type": ["null", "string"] - }, - "amount": { - "type": ["null", "integer"] - }, - "cvc_check": { - "type": ["null", "string"] - }, - "usage": { - "type": ["null", "string"] - }, - "address_line1": { - "type": ["null", "string"] - }, - "owner": { - "type": ["null", "object"], - "properties": { - "verified_address": { - "type": ["null", "string"] - }, - "email": { - "type": ["null", "string"] - }, - "address": { - "type": ["null", "object"], - "properties": { - "line2": { - "type": ["null", "string"] - }, - "state": { - "type": ["null", "string"] - }, - "city": { - "type": ["null", "string"] - }, - "postal_code": { - "type": ["null", "string"] - }, - "country": { - "type": ["null", "string"] - }, - "line1": { - "type": ["null", "string"] - } - } - }, - "verified_email": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "phone": { - "type": ["null", "string"] - }, - "verified_name": { - "type": ["null", "string"] - }, - "verified_phone": { - "type": ["null", "string"] - } - } - }, - "tokenization_method": { - "type": ["null", "string"] - }, - "client_secret": { - "type": ["null", "string"] - }, - "fingerprint": { - "type": ["null", "string"] - }, - "address_city": { - "type": ["null", "string"] - }, - "currency": { - "type": ["null", "string"] - }, - "address_line1_check": { - "type": ["null", "string"] - }, - "receiver": { - "type": ["null", "object"], - "properties": { - "refund_attributes_method": { - "type": ["null", "string"] - }, - "amount_returned": { - "type": ["null", "integer"] - }, - "amount_received": { - "type": ["null", "integer"] - }, - "refund_attributes_status": { - "type": ["null", "string"] - }, - "address": { - "type": ["null", "string"] - }, - "amount_charged": { - "type": ["null", "integer"] - } - } - }, - "flow": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "ach_credit_transfer": { - "type": ["null", "object"], - "properties": { - "bank_name": { - "type": ["null", "string"] - }, - "fingerprint": { - "type": ["null", "string"] - }, - "routing_number": { - "type": ["null", "string"] - }, - "swift_code": { - "type": ["null", "string"] - }, - "refund_account_holder_type": { - "type": ["null", "string"] - }, - "refund_account_holder_name": { - "type": ["null", "string"] - }, - "refund_account_number": { - "type": ["null", "string"] - }, - "refund_routing_number": { - "type": ["null", "string"] - }, - "account_number": { - "type": ["null", "string"] - } - } - }, - "customer": { - "type": ["null", "string"] - }, - "address_zip_check": { - "type": ["null", "string"] - }, - "status": { - "type": ["null", "string"] - }, - "created": { - "type": ["null", "string"], - "format": "date-time" - }, - "address_state": { - "type": ["null", "string"] - }, - "alipay": { - "type": ["null", "object"], - "properties": {} - }, - "bancontact": { - "type": ["null", "object"], - "properties": {} - }, - "eps": { - "type": ["null", "object"], - "properties": {} - }, - "ideal": { - "type": ["null", "object"], - "properties": {} - }, - "multibanco": { - "type": ["null", "object"], - "properties": {} - }, - "redirect": { - "type": ["null", "object"], - "properties": { - "failure_reason": { - "type": ["null", "string"] - }, - "return_url": { - "type": ["null", "string"] - }, - "status": { - "type": ["null", "string"] - }, - "url": { - "type": ["null", "string"] - } - } - } - } - } - }, - { - "type": ["null", "object"], - "properties": { - "metadata": { - "type": ["null", "object"], - "properties": {} - }, - "type": { - "type": ["null", "string"] - }, - "address_zip": { - "type": ["null", "string"] - }, - "livemode": { - "type": ["null", "boolean"] - }, - "card": { - "type": ["null", "object"], - "properties": { - "fingerprint": { - "type": ["null", "string"] - }, - "last4": { - "type": ["null", "string"] - }, - "dynamic_last4": { - "type": ["null", "string"] - }, - "address_line1_check": { - "type": ["null", "string"] - }, - "exp_month": { - "type": ["null", "integer"] - }, - "tokenization_method": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "exp_year": { - "type": ["null", "integer"] - }, - "three_d_secure": { - "type": ["null", "string"] - }, - "funding": { - "type": ["null", "string"] - }, - "brand": { - "type": ["null", "string"] - }, - "cvc_check": { - "type": ["null", "string"] - }, - "country": { - "type": ["null", "string"] - }, - "address_zip_check": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "string"] - } - } - }, - "statement_descriptor": { - "type": ["null", "string"] - }, - "id": { - "type": ["null", "string"] - }, - "address_country": { - "type": ["null", "string"] - }, - "funding": { - "type": ["null", "string"] - }, - "dynamic_last4": { - "type": ["null", "string"] - }, - "exp_year": { - "type": ["null", "integer"] - }, - "last4": { - "type": ["null", "string"] - }, - "exp_month": { - "type": ["null", "integer"] - }, - "brand": { - "type": ["null", "string"] - }, - "address_line2": { - "type": ["null", "string"] - }, - "country": { - "type": ["null", "string"] - }, - "object": { - "type": ["null", "string"] - }, - "amount": { - "type": ["null", "integer"] - }, - "cvc_check": { - "type": ["null", "string"] - }, - "usage": { - "type": ["null", "string"] - }, - "address_line1": { - "type": ["null", "string"] - }, - "owner": { - "type": ["null", "object"], - "properties": { - "verified_address": { - "type": ["null", "string"] - }, - "email": { - "type": ["null", "string"] - }, - "address": { - "type": ["null", "object"], - "properties": { - "line2": { - "type": ["null", "string"] - }, - "state": { - "type": ["null", "string"] - }, - "city": { - "type": ["null", "string"] - }, - "postal_code": { - "type": ["null", "string"] - }, - "country": { - "type": ["null", "string"] - }, - "line1": { - "type": ["null", "string"] - } - } - }, - "verified_email": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "phone": { - "type": ["null", "string"] - }, - "verified_name": { - "type": ["null", "string"] - }, - "verified_phone": { - "type": ["null", "string"] - } - } - }, - "tokenization_method": { - "type": ["null", "string"] - }, - "client_secret": { - "type": ["null", "string"] - }, - "fingerprint": { - "type": ["null", "string"] - }, - "address_city": { - "type": ["null", "string"] - }, - "currency": { - "type": ["null", "string"] - }, - "address_line1_check": { - "type": ["null", "string"] - }, - "receiver": { - "type": ["null", "object"], - "properties": { - "refund_attributes_method": { - "type": ["null", "string"] - }, - "amount_returned": { - "type": ["null", "integer"] - }, - "amount_received": { - "type": ["null", "integer"] - }, - "refund_attributes_status": { - "type": ["null", "string"] - }, - "address": { - "type": ["null", "string"] - }, - "amount_charged": { - "type": ["null", "integer"] - } - } - }, - "flow": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "ach_credit_transfer": { - "type": ["null", "object"], - "properties": { - "bank_name": { - "type": ["null", "string"] - }, - "fingerprint": { - "type": ["null", "string"] - }, - "routing_number": { - "type": ["null", "string"] - }, - "swift_code": { - "type": ["null", "string"] - }, - "refund_account_holder_type": { - "type": ["null", "string"] - }, - "refund_account_holder_name": { - "type": ["null", "string"] - }, - "refund_account_number": { - "type": ["null", "string"] - }, - "refund_routing_number": { - "type": ["null", "string"] - }, - "account_number": { - "type": ["null", "string"] - } - } - }, - "customer": { - "type": ["null", "string"] - }, - "address_zip_check": { - "type": ["null", "string"] - }, - "status": { - "type": ["null", "string"] - }, - "created": { - "type": ["null", "string"], - "format": "date-time" - }, - "address_state": { - "type": ["null", "string"] - }, - "alipay": { - "type": ["null", "object"], - "properties": {} - }, - "bancontact": { - "type": ["null", "object"], - "properties": {} - }, - "eps": { - "type": ["null", "object"], - "properties": {} - }, - "ideal": { - "type": ["null", "object"], - "properties": {} - }, - "multibanco": { - "type": ["null", "object"], - "properties": {} - }, - "redirect": { - "type": ["null", "object"], - "properties": { - "failure_reason": { - "type": ["null", "string"] - }, - "return_url": { - "type": ["null", "string"] - }, - "status": { - "type": ["null", "string"] - }, - "url": { - "type": ["null", "string"] - } - } - } - } - } - ] - }, - "delinquent": { - "type": ["null", "boolean"] - }, - "description": { - "type": ["null", "string"] - }, - "livemode": { - "type": ["null", "boolean"] - }, - "default_source": { - "type": ["null", "string"] - }, - "cards": { - "type": ["null", "array"], - "items": { - "type": ["null", "object"], - "properties": { - "metadata": { - "type": ["null", "object"], - "properties": {} - }, - "object": { - "type": ["null", "string"] - }, - "id": { - "type": ["null", "string"] - }, - "exp_month": { - "type": ["null", "integer"] - }, - "dynamic_last4": { - "type": ["null", "string"] - }, - "exp_year": { - "type": ["null", "integer"] - }, - "last4": { - "type": ["null", "string"] - }, - "funding": { - "type": ["null", "string"] - }, - "brand": { - "type": ["null", "string"] - }, - "country": { - "type": ["null", "string"] - }, - "customer": { - "type": ["null", "string"] - }, - "cvc_check": { - "type": ["null", "string"] - }, - "address_line2": { - "type": ["null", "string"] - }, - "address_line1": { - "type": ["null", "string"] - }, - "fingerprint": { - "type": ["null", "string"] - }, - "address_zip": { - "type": ["null", "string"] - }, - "address_city": { - "type": ["null", "string"] - }, - "address_country": { - "type": ["null", "string"] - }, - "address_line1_check": { - "type": ["null", "string"] - }, - "tokenization_method": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "address_state": { - "type": ["null", "string"] - }, - "address_zip_check": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "string"] - } - } - } - }, - "email": { - "type": ["null", "string"] - }, - "default_card": { - "type": ["null", "string"] - }, - "subscriptions": { - "type": ["null", "array"], - "items": { - "type": ["null", "string"] - } - }, - "discount": { - "type": ["null", "object"], - "properties": { - "end": { - "type": ["null", "string"], - "format": "date-time" - }, - "coupon": { - "type": ["null", "object"], - "properties": { - "metadata": { - "type": ["null", "object"], - "properties": {} - }, - "valid": { - "type": ["null", "boolean"] - }, - "livemode": { - "type": ["null", "boolean"] - }, - "amount_off": { - "type": ["null", "integer"] - }, - "redeem_by": { - "type": ["null", "string"], - "format": "date-time" - }, - "duration_in_months": { - "type": ["null", "integer"] - }, - "percent_off_precise": { - "type": ["null", "number"] - }, - "max_redemptions": { - "type": ["null", "integer"] - }, - "currency": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "times_redeemed": { - "type": ["null", "integer"] - }, - "id": { - "type": ["null", "string"] - }, - "duration": { - "type": ["null", "string"] - }, - "object": { - "type": ["null", "string"] - }, - "percent_off": { - "type": ["null", "integer"] - }, - "created": { - "type": ["null", "string"], - "format": "date-time" - } - } - }, - "customer": { - "type": ["null", "string"] - }, - "start": { - "type": ["null", "string"], - "format": "date-time" - }, - "object": { - "type": ["null", "string"] - }, - "subscription": { - "type": ["null", "string"] - } - } - }, - "account_balance": { - "type": ["null", "integer"] - }, - "currency": { - "type": ["null", "string"] - }, - "id": { - "type": ["null", "string"] - }, - "invoice_prefix": { - "type": ["null", "string"] - }, - "tax_info_verification": { - "type": ["null", "string"] - }, - "object": { - "type": ["null", "string"] - }, - "created": { - "type": ["null", "string"], - "format": "date-time" - }, - "tax_info": { - "type": ["null", "string"] - }, - "updated": { - "type": ["null", "string"], - "format": "date-time" - } - } - } - } - ] -} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_recipe.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_recipe.json deleted file mode 100644 index d70379ae858bd..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/one_recipe.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "Name": "Christmas pie", - "url": "https://www.bbcgoodfood.com/recipes/2793/christmas-pie", - "Description": "Combine a few key Christmas flavours here to make a pie that both children and adults will adore", - "Author": "Mary Cadogan", - "Ingredients": [ - { - "quantity": "2 tbsp", - "ingredient": "olive oil", - "nutritionfacts": { - "calories": 119, - "fat": "13.5g" - } - }, - { - "quantity": "knob", - "ingredient": "butter", - "nutritionfacts": { - "calories": 102, - "fat": "11.5g" - } - }, - { - "quantity": "1", - "ingredient": "onion", - "preparation": "finely chopped", - "nutritionfacts": { - "calories": 102, - "fat": "11.5g" - } - }, - { - "quantity": "500g", - "ingredient": "sausagemeat or skinned sausages", - "nutritionfacts": { - "calories": 268, - "fat": "18g" - } - }, - { - "quantity": "1", - "ingredient": "lemon", - "preparation": "grated zest", - "nutritionfacts": { - "calories": 13 - } - }, - { - "quantity": "100g", - "ingredient": "fresh white breadcrumbs" - }, - { - "quantity": "85g", - "ingredient": "ready-to-eat dried apricots", - "preparation": "chopped", - "nutritionfacts": { - "calories": 34, - "fat": "0.27g" - } - }, - { - "quantity": "58g", - "ingredient": "chestnut, canned or vacuum-packed", - "preparation": "chopped", - "nutritionfacts": { - "calories": 77, - "fat": "1g" - } - }, - { - "quantity": "2 tsp", - "ingredient": "fresh or dried thyme", - "preparation": "chopped" - }, - { - "quantity": "100g", - "ingredient": "cranberries, fresh or frozen" - }, - { - "quantity": "500g", - "ingredient": "boneless, skinless chicken breasts", - "nutritionfacts": { - "calories": 284, - "fat": "6.2g" - } - }, - { - "quantity": "500g", - "ingredient": "pack ready-made shortcrust pastry" - }, - { - "quantity": "1", - "ingredient": "beaten egg", - "preparation": "to glaze", - "nutritionfacts": { - "calories": 75, - "fat": "5g" - } - } - ], - "Method": [ - "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", - "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.", - "Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.", - "Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.", - "Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles." - ] -} \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/profiles.yml b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/profiles.yml deleted file mode 100755 index 0504166176eaf..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/profiles.yml +++ /dev/null @@ -1,40 +0,0 @@ - -# This configuration file specifies information about connections to -# your data warehouse(s). The file contains a series of "profiles." -# Profiles specify database credentials and connection information -# - -# Top-level configs that apply to all profiles are set here -config: - partial_parse: true - printer_width: 120 - send_anonymous_usage_stats: False - use_colors: True - -# see https://docs.getdbt.com/docs/supported-databases for more details -# -# Commonly, it's helpful to define multiple targets for a profile. For example, -# these targets might be `dev` and `prod`. Whereas the `dev` target points to -# a development schema (eg. dbt_dev), the `prod` schema should point to the -# prod schema (eg. analytics). Analytical/BI tools should point to the -# prod schema so that local development does not interfere with analysis. -# - -dev: - target: dev # default target is dev unless changed at run time - outputs: - dev: - type: bigquery - method: service-account - project: airbytesandbox - schema: dbt_chris - threads: 32 - timeout_seconds: 300 - location: europe-west1 - priority: interactive - keyfile: /home/user/Workspace/secrets/gcs.json - retries: 2 - # If a query would bill more than a gigabyte of data, then - # BigQuery will reject the query - # maximum_bytes_billed: 1000000000 - maximum_bytes_billed: 10000000 diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/recipes.json b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/recipes.json deleted file mode 100644 index 655ec2518c025..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/sample_files/recipes.json +++ /dev/null @@ -1,10 +0,0 @@ -{"Name": "Christmas pie", "url": "https://www.bbcgoodfood.com/recipes/2793/christmas-pie", "Description": "Combine a few key Christmas flavours here to make a pie that both children and adults will adore", "Author": "Mary Cadogan", "Ingredients": ["2 tbsp olive oil", "knob butter", "1 onion, finely chopped", "500g sausagemeat or skinned sausages", "grated zest of 1 lemon", "100g fresh white breadcrumbs", "85g ready-to-eat dried apricots, chopped", "50g chestnut, canned or vacuum-packed, chopped", "2 tsp chopped fresh or 1tsp dried thyme", "100g cranberries, fresh or frozen", "500g boneless, skinless chicken breasts", "500g pack ready-made shortcrust pastry", "beaten egg, to glaze"], "Method": ["Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.", "Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.", "Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.", "Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles."]} -{"Name": "Simmer-&-stir Christmas cake", "url": "https://www.bbcgoodfood.com/recipes/1160/simmerandstir-christmas-cake", "Description": "An easy-to-make alternative to traditional Christmas cakes which requires no beating", "Author": "Mary Cadogan", "Ingredients": ["175g butter, chopped", "200g dark muscovado sugar", "750g luxury mixed dried fruit (one that includes mixed peel and glac\u00e9 cherries)", "finely grated zest and juice of 1 orange", "finely grated zest of 1 lemon", "100ml/3\u00bd fl oz cherry brandy or brandy plus 4tbsp more", "85g macadamia nut", "3 large eggs, lightly beaten", "85g ground almond", "200g plain flour", "\u00bd tsp baking powder", "1 tsp ground mixed spice", "1 tsp ground cinnamon", "\u00bc tsp ground allspice"], "Method": ["Put the butter, sugar, fruit, zests, juice and 100ml/3\u00bdfl oz brandy in a large pan. Bring slowly to the boil, stirring until the butter has melted. Reduce the heat and bubble for 10 minutes, stirring occasionally.", "Remove the pan from the heat and leave to cool for 30 minutes.", "Meanwhile, preheat the oven to 150C/gas 2/ fan 130C and line a 20cm round cake tin. Toast the nuts in a dry frying pan, tossing them until evenly browned, or in the oven for 8-10 minutes - keep an eye on them as they burn easily. When they are cool, chop roughly. Stir the eggs, nuts and ground almonds into the fruit mixture and mix well. Sift the flour, baking powder and spices into the pan. Stir in gently, until there are no traces of flour left.", "Spoon the mixture into the tin and smooth it down evenly - you will find this is easiest with the back of a metal spoon which has been dipped into boiling water.", "Bake for 45 minutes, then turn down the heat to 140C/gas 1/ fan120C and cook for a further 1-1\u00bc hours (about a further 1\u00be hours if you have a gas oven) until the cake is dark golden in appearance and firm to the touch. Cover the top of the cake with foil if it starts to darken too much. To check the cake is done, insert a fine skewer into the centre - if it comes out clean, the cake is cooked.", "Make holes all over the warm cake with a fine skewer and spoon the extra 4tbsp brandy over the holes until it has all soaked in. Leave the cake to cool in the tin. When it's cold, remove it from the tin, peel off the lining paper, then wrap first in baking parchment and then in foil. The cake will keep in a cupboard for up to three months or you can freeze it for six months."]} -{"Name": "Christmas cupcakes", "url": "https://www.bbcgoodfood.com/recipes/72622/christmas-cupcakes", "Description": "These beautiful and classy little cakes make lovely gifts, and kids will enjoy decorating them too", "Author": "Sara Buenfeld", "Ingredients": ["200g dark muscovado sugar", "175g butter, chopped", "700g luxury mixed dried fruit", "50g glac\u00e9 cherries", "2 tsp grated fresh root ginger", "zest and juice 1 orange", "100ml dark rum, brandy or orange juice", "85g/3oz pecannuts, roughly chopped", "3 large eggs, beaten", "85g ground almond", "200g plain flour", "\u00bd tsp baking powder", "1 tsp mixed spice", "1 tsp cinnamon", "400g pack ready-rolled marzipan(we used Dr Oetker)", "4 tbsp warm apricotjam or shredless marmalade", "500g pack fondant icingsugar", "icing sugar, for dusting", "6 gold and 6 silver muffincases", "6 gold and 6 silver sugared almonds", "snowflake sprinkles"], "Method": ["Tip the sugar, butter, dried fruit, whole cherries, ginger, orange zest and juice into a large pan. Pour over the rum, brandy or juice, then put on the heat and slowly bring to the boil, stirring frequently to melt the butter. Reduce the heat and bubble gently, uncovered for 10 mins, stirring every now and again to make sure the mixture doesn\u2019t catch on the bottom of the pan. Set aside for 30 mins to cool.", "Stir the nuts, eggs and ground almonds into the fruit, then sift in the flour, baking powder and spices. Stir everything together gently but thoroughly. Your batter is ready.", "Heat oven to 150C/130C fan/gas 2. Scoop the cake mix into 12 deep muffin cases (an ice-cream scoop works well), then level tops with a spoon dipped in hot water. Bake for 35-45 mins until golden and just firm to touch. A skewer inserted should come out clean. Cool on a wire rack.", "Unravel the marzipan onto a work surface lightly dusted with icing sugar. Stamp out 12 rounds, 6cm across. Brush the cake tops with apricot jam, top with a marzipan round and press down lightly.", "Make up the fondant icing to a spreading consistency, then swirl on top of each cupcake. Decorate with sugared almonds and snowflakes, then leave to set. Will keep in a tin for 3 weeks."]} -{"Name": "Christmas buns", "url": "https://www.bbcgoodfood.com/recipes/1803633/christmas-buns", "Description": "Paul Hollywood's fruit rolls can be made ahead then heated up before adding a glossy glaze and citrus icing", "Author": "Paul Hollywood", "Ingredients": ["500g strong white flour, plus extra for dusting", "7g sachet fast-action dried yeast", "300ml milk", "40g unsalted butter, softened at room temperature", "1 egg", "vegetable oil, for greasing", "25g unsalted butter, melted", "75g soft brown sugar", "2 tsp ground cinnamon", "100g dried cranberries", "100g chopped dried apricot", "50g caster sugar", "zest 1 lemon", "200g icing sugar"], "Method": ["Put the flour and 1 tsp salt into a large bowl. Make a well in the centre and add the yeast. Meanwhile, warm the milk and butter in a pan until the butter melts and the mixture is lukewarm. Add the milk mixture and egg to the flour mixture and stir until the contents come together as a soft dough (add extra flour if you need to).", "Tip the dough onto a well-floured surface. Knead for 5 mins, adding more flour if necessary, until the dough is smooth, elastic and no longer sticky.", "Lightly oil a bowl with the vegetable oil. Place the dough in the bowl and turn until covered in oil. Cover the bowl with cling film and set aside in a warm place for 1 hr or until doubled in size. Lightly grease a baking sheet and set aside.", "For the filling, knock the dough back to its original size and turn out onto a lightly floured surface. Roll it into a 1cm-thick rectangle. Brush all over with the melted butter, then sprinkle over the sugar, cinnamon and fruit.", "Roll up the dough into a tight cylinder, cut into 9 x 4cm slices and position on the prepared baking sheet, leaving a little space between. Cover with a tea towel and set aside to rise for 30 mins.", "Heat oven to 190C/170C fan/gas 5. Bake the buns for 20-25 mins or until risen and golden brown. Meanwhile, melt the glaze sugar with 4 tbsp water until syrupy.", "Remove from oven and glaze. Set aside to cool on a wire rack. Once cool, mix the zest and icing sugar with about 2 tbsp water to drizzle over the buns. Serve."]} -{"Name": "Christmas cupcakes", "url": "https://www.bbcgoodfood.com/recipes/981634/christmas-cupcakes", "Description": "Made these for the second time today, and I have to say they turned out great! I've got large muffin tins and the mixture made 15 muffins, will definetely make these again at christmas time and decorate festively.", "Author": "Barney Desmazery", "Ingredients": ["280g self-raising flour", "175g golden caster sugar", "175g unsalted butter, very soft", "150g pot fat-free natural yogurt", "1 tsp vanilla extract", "3 eggs", "85g unsalted butter, softened", "1 tsp vanilla extract", "200g icing sugar, sifted", "natural green food colouring(for Christmas trees), sweets, sprinkles and white chocolate stars", "milk and white chocolatebuttons and natural colouring icing pens, available at Asda"], "Method": ["Heat oven to 190C/170 fan/gas 5 and line a 12-hole muffin tin with cake cases. Put all the cake ingredients into a bowl and mix with a whisk until smooth. Spoon the mix into the cases, bake for 25 mins until golden and risen and a skewer comes out clean. Cool on a wire rack.", "For the frosting, beat the butter, vanilla extract and icing sugar until pale and creamy and completely combined. To make snowmen, reindeer and Christmas puddings, first spread the icing over the top of each cake. Then lay the chocolate buttons on top, slicing some buttons into quarters to make ears and hats. Finally, use icing pens for the details. For the Christmas tree, colour the icing with green food colouring and pipe onto the cakes using a star-shaped nozzle, decorate with sweets, sprinkles and white chocolate stars."]} -{"Name": "Christmas slaw", "url": "https://www.bbcgoodfood.com/recipes/890635/christmas-slaw", "Description": "A nutty winter salad which is superhealthy, quick to prepare and finished with a light maple syrup dressing", "Author": "Good Food", "Ingredients": ["2 carrots, halved", "\u00bd white cabbage, shredded", "100g pecans, roughly chopped", "bunch spring onions, sliced", "2 red peppers, deseeded and sliced", "2 tbsp maple syrup", "2 tsp Dijon mustard", "8 tbsp olive oil", "4 tbsp cider vinegar"], "Method": ["Peel strips from the carrots using a vegetable peeler, then mix with the other salad ingredients in a large bowl. Combine all the dressing ingredients in a jam jar, season, then put the lid on and shake well. Toss through the salad when you\u2019re ready to eat it. The salad and dressing will keep separately in the fridge for up to four days."]} -{"Name": "Christmas mess", "url": "https://www.bbcgoodfood.com/recipes/2806664/christmas-mess", "Description": "Delicious and a synch to make! Have made this a couple of times as a dinner party dessert, very pretty as a winter alternative to Eton mess. The fact you use frozen fruits is great, I just used a bog standard pack of frozen mixed berries and added some home made blackberry liqueur. Like other people, I added more cinnamon. Thumbs up!", "Author": "Caroline Hire", "Ingredients": ["600ml double cream", "400g Greek yoghurt", "4 tbsp lemon curd", "1 x 500g bag frozen mixed berries(we used Sainsbury's Black Forest fruits)", "4 tbsp icing sugar", "2 tbsp cassis(optional)", "1 pinch cinnamon", "8 meringuenests"], "Method": ["In a small saucepan gently heat the frozen berries, icing sugar and cinnamon until the sugar has dissolved. Remove from the heat, stir in the cassis, if using, and set aside to cool completely.", "Whip the double cream and Greek yogurt until just holding it\u2019s shape, ripple through the lemon curd. Break the meringue nests into a glass bowl, or 8 individual glasses. Spoon over half the cream, then half the berries. Repeat with the remaining cream and berries. Serve immediately."]} -{"Name": "Christmas brownies", "url": "https://www.bbcgoodfood.com/recipes/christmas-brownies", "Description": "can I made these the day before", "Author": "Miriam Nice", "Ingredients": ["200g unsalted buttercut into cubes, plus extra for greasing", "100g dark chocolate, chopped", "100g milk chocolate, chopped", "3 large eggs", "300g golden caster sugar", "100g plain flour", "50g cocoa powder", "\u00bd tsp mixed spice", "9 sprigs rosemary", "9 glac\u00e9 cherries", "1 egg white", "2 tbsp caster sugar", "4 amaretti biscuits, crushed", "9 chocolate truffles(we used Lindt lindor)", "edible gold lustre spray", "1-2 tsp icing sugarfor dusting", "few chocolate buttons", "edible silver balls"], "Method": ["Grease and line a 20cm x 20cm brownie tin. Heat oven to 180C/160C fan/gas 4. Put the butter and both types of chocolate in a heat proof bowl and either melt in the microwave (in 30 second bursts, stirring after each) or set over a pan of barely simmering water, stirring every now and then until the chocolate has melted.", "Leave the chocolate and butter mixture to cool a little while you whisk the eggs and caster sugar in a large bowl using electric beaters. Once the mixture is pale, fluffy and looks like it\u2019s roughly\u00a0doubled in volume, whisk in the melted chocolate. Fold in the flour, cocoa powder and mixed spice until no pockets of flour remain\u00a0then pour\u00a0into your prepared tin. Level the top\u00a0with a spatula and bake for 20-25 mins. The top should look set and shiny but should be a little wobbly if you gently jostle the tin.", "Leave the brownie to cool completely in the tin then chill in the fridge until set. While the brownie cools brush the rosemary sprigs and glac\u00e9\u00a0cherries with egg white, dab off the excess with kitchen paper then dredge in caster sugar until well coated. Leave to dry on a wire rack. Put the chocolate truffles onto a sheet of baking paper or foil then spray with the edible gold lustre.", "Dust the chilled brownie with icing sugar to create a snowy surface and top with amaretti biscuit pieces then poke the crystalised rosemary sprigs into the surface at random intervals (cut the brownie into pieces and dust on icing sugar first, if you like). Nestle a glac\u00e9 cherry or gold truffle alongside the rosemary sprig then add the buttons and silver balls."]} -{"Name": "Christmas cosmopolitan", "url": "https://www.bbcgoodfood.com/recipes/889643/christmas-cosmopolitan", "Description": "How many servings have you gotten using the above measures?", "Author": "Good Food", "Ingredients": ["500ml vodka", "500ml gingerwine", "1l cranberry juice", "juice 5 limes, keep zest for garnish", "sliced stem ginger"], "Method": ["Mix the vodka and ginger wine in a jug. Stir in the cranberry juice, lime juice and some sliced stem ginger. Garnish with lime zest, if you like."]} -{"Name": "Christmas pizza", "url": "https://www.bbcgoodfood.com/recipes/christmas-pizza", "Description": "Use up leftover roast turkey and sausagemeat stuffing in this new spin on an Italian classic", "Author": "Katy Greenwood", "Ingredients": ["145g pizza basemix", "6 tbsp tomato pasta sauce", "large handful (about 100g) leftover stuffing(a sausage stuffing works well for this)", "large handful (about 100g) leftover cooked turkey, shredded", "100g mozzarella, sliced", "small pack sage, leaves picked", "1 tbsp olive oil"], "Method": ["Heat oven to 220C/200C fan/gas 7. Prepare the pizza base mix following pack instructions. Once rolled out, leave to rest for 10 mins, then top with the pasta sauce.", "Scatter over the stuffing and turkey, then top with the mozzarella. Toss the sage leaves with the oil, then scatter over the pizza, drizzling over any remaining oil. Bake for 10-12 mins until the crust is crisp and the cheese has melted."]} diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py index 9b3fd7102b4ae..ede9bda5db6ad 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py @@ -25,33 +25,40 @@ import argparse import json import os -from typing import Tuple, Union, Optional +from typing import Optional, Tuple, Union class TransformCatalog: + """ +To run this transformation: +``` +python3 main_dev_transform_catalog.py \ + --catalog integration_tests/catalog.json \ + --out normalization/dbt-transform/models/generated/ \ + --json json_blob \ + --table airbytesandbox.data.one_recipe_json +``` + """ + config: dict = {} def run(self, args): self.parse(args) - catalog = self.read_json_catalog() - result = generate_dbt_model(catalog=catalog, json_col="json_blob", from_table="`airbytesandbox.data.one_recipe_json`") - self.output_sql_models(result) + for catalog_file in self.config["catalog"]: + print(f"Processing {catalog_file}...") + catalog = read_json_catalog(catalog_file) + result = generate_dbt_model(catalog=catalog, json_col=self.config["json"], from_table=self.config["table"]) + self.output_sql_models(result) def parse(self, args): parser = argparse.ArgumentParser(add_help=False) - parser.add_argument("--catalog", type=str, required=True, help="path to Catalog (JSON Schema) file") + parser.add_argument("--catalog", nargs="+", type=str, required=True, help="path to Catalog (JSON Schema) file") parser.add_argument("--out", type=str, required=True, help="path to output generated DBT Models to") + parser.add_argument("--json", type=str, required=True, help="name of the column containing the json blob") + parser.add_argument("--table", type=str, required=True, help="schema and table name containing the json blob") parsed_args = parser.parse_args(args) - self.config = { - "catalog": parsed_args.catalog, - "output_path": parsed_args.out, - } - - def read_json_catalog(self) -> dict: - input_path = self.config["catalog"] - with open(input_path, "r") as file: - contents = file.read() - return json.loads(contents) + self.config = {"catalog": parsed_args.catalog, "output_path": parsed_args.out, "json": parsed_args.json, "table": parsed_args.table} + print(self.config) def output_sql_models(self, result: dict): output = self.config["output_path"] @@ -64,6 +71,12 @@ def output_sql_models(self, result: dict): f.write(sql) +def read_json_catalog(input_path: str) -> dict: + with open(input_path, "r") as file: + contents = file.read() + return json.loads(contents) + + def is_string(property_type) -> bool: return property_type == "string" or "string" in property_type @@ -198,7 +211,7 @@ def process_node(path: str, json_col: str, name: str, properties: dict, from_tab ) node_sql = f"""{prefix} {name}_node as ( - select + select {inject_cols} {node_columns} from {from_table} @@ -220,7 +233,7 @@ def process_node(path: str, json_col: str, name: str, properties: dict, from_tab child_col, join_child_table = json_extract_nested_property(path=path, json_col=json_col, name=col, definition=properties[col]) child_sql = f"""{prefix} {name}_node as ( - select + select {child_col}, {node_columns} from {from_table} @@ -260,7 +273,11 @@ def process_node(path: str, json_col: str, name: str, properties: dict, from_tab def generate_dbt_model(catalog: dict, json_col: str, from_table: str) -> dict: result = {} for obj in catalog["streams"]: - name = obj["name"] + name = "undefined" + if "name" in obj: + name = obj["name"] + elif "stream" in obj: + name = obj["stream"] properties = {} if "json_schema" in obj: properties = obj["json_schema"]["properties"] From ec8f803284bf30a2a8d982e010ca7360af80a309 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 4 Nov 2020 16:40:53 +0100 Subject: [PATCH 04/18] Make generated DBT SQL cross db compatible --- .gitignore | 1 + .../macros/cross_db_utils/array.sql | 29 ++++++++ .../macros/cross_db_utils/json_operations.sql | 72 +++++++++++++++++++ .../normalization/dbt-transform/packages.yml | 5 ++ .../transform_catalog/transform.py | 44 ++++++------ 5 files changed, 131 insertions(+), 20 deletions(-) create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/macros/cross_db_utils/array.sql create mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/macros/cross_db_utils/json_operations.sql create mode 100755 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/packages.yml diff --git a/.gitignore b/.gitignore index 7f689e5320765..665e0a1abba22 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ __pycache__ # dbt airbyte-integrations/bases/base-normalization/normalization/dbt-transform/logs/ airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/generated +airbyte-integrations/bases/base-normalization/normalization/dbt-transform/dbt_modules/ diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/macros/cross_db_utils/array.sql b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/macros/cross_db_utils/array.sql new file mode 100644 index 0000000000000..482053eab2f60 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/macros/cross_db_utils/array.sql @@ -0,0 +1,29 @@ +{# + Adapter Macros for the following functions: + - Bigquery: unnest() -> https://cloud.google.com/bigquery/docs/reference/standard-sql/arrays#flattening-arrays-and-repeated-fields + - Snowflake: flatten() -> https://docs.snowflake.com/en/sql-reference/functions/flatten.html + - Redshift: -> https://blog.getdbt.com/how-to-unnest-arrays-in-redshift/ +#} + +{# flatten ------------------------------------------------- #} + +{% macro unnest(array_col) -%} + {{ adapter.dispatch('unnest')(array_col) }} +{%- endmacro %} + +{% macro default__unnest(array_col) -%} + unnest({{ array_col }}) +{%- endmacro %} + +{% macro bigquery__unnest(array_col) -%} + unnest({{ array_col }}) +{%- endmacro %} + +{% macro redshift__unnest(array_col) -%} + -- FIXME to implement as described here? https://blog.getdbt.com/how-to-unnest-arrays-in-redshift/ +{%- endmacro %} + +{% macro snowflake__unnest(array_col) -%} + -- TODO test this!! not so sure yet... + table(flatten({{ array_col }})) +{%- endmacro %} \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/macros/cross_db_utils/json_operations.sql b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/macros/cross_db_utils/json_operations.sql new file mode 100644 index 0000000000000..f3ab19731979e --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/macros/cross_db_utils/json_operations.sql @@ -0,0 +1,72 @@ +{# + Adapter Macros for the following functions: + - Bigquery: JSON_EXTRACT(json_string_expr, json_path_format) -> https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions + - Snowflake: JSON_EXTRACT_PATH_TEXT( , '' ) -> https://docs.snowflake.com/en/sql-reference/functions/json_extract_path_text.html + - Redshift: json_extract_path_text('json_string', 'path_elem' [,'path_elem'[, …] ] [, null_if_invalid ] ) -> https://docs.aws.amazon.com/redshift/latest/dg/JSON_EXTRACT_PATH_TEXT.html +#} + +{# json_extract ------------------------------------------------- #} + +{% macro json_extract(json_column, json_path) -%} + {{ adapter.dispatch('json_extract')(json_column, json_path) }} +{%- endmacro %} + +{% macro default__json_extract(json_column, json_path) -%} + json_extract({{json_column}}, {{json_path}}) +{%- endmacro %} + +{% macro bigquery__json_extract(json_column, json_path) -%} + json_extract({{json_column}}, {{json_path}}) +{%- endmacro %} + +{% macro redshift__json_extract(json_column, json_path) -%} + json_extract_path_text({{json_column}}, {{json_path}}) +{%- endmacro %} + +{% macro snowflake__json_extract(json_column, json_path) -%} + json_extract_path_text({{json_column}}, {{json_path}}) +{%- endmacro %} + +{# json_extract_scalar ------------------------------------------------- #} + +{% macro json_extract_scalar(json_column, json_path) -%} + {{ adapter.dispatch('json_extract_scalar')(json_column, json_path) }} +{%- endmacro %} + +{% macro default__json_extract_scalar(json_column, json_path) -%} + json_extract_scalar({{json_column}}, {{json_path}}) +{%- endmacro %} + +{% macro bigquery__json_extract_scalar(json_column, json_path) -%} + json_extract_scalar({{json_column}}, {{json_path}}) +{%- endmacro %} + +{% macro redshift__json_extract_scalar(json_column, json_path) -%} + json_extract_path_text({{json_column}}, {{json_path}}) +{%- endmacro %} + +{% macro snowflake__json_extract_scalar(json_column, json_path) -%} + json_extract_path_text({{json_column}}, {{json_path}}) +{%- endmacro %} + +{# json_extract_array ------------------------------------------------- #} + +{% macro json_extract_array(json_column, json_path) -%} + {{ adapter.dispatch('json_extract_array')(json_column, json_path) }} +{%- endmacro %} + +{% macro default__json_extract_array(json_column, json_path) -%} + json_extract_array({{json_column}}, {{json_path}}) +{%- endmacro %} + +{% macro bigquery__json_extract_array(json_column, json_path) -%} + json_extract_array({{json_column}}, {{json_path}}) +{%- endmacro %} + +{% macro redshift__json_extract_array(json_column, json_path) -%} + json_extract_path_text({{json_column}}, {{json_path}}) +{%- endmacro %} + +{% macro snowflake__json_extract_array(json_column, json_path) -%} + json_extract_path_text({{json_column}}, {{json_path}}) +{%- endmacro %} diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/packages.yml b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/packages.yml new file mode 100755 index 0000000000000..e73e9a48e6184 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/packages.yml @@ -0,0 +1,5 @@ +# add dependencies. these will get pulled during the `dbt deps` process. + +packages: + - git: "https://github.com/fishtown-analytics/dbt-utils.git" + revision: 0.6.2 \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py index ede9bda5db6ad..bb38d08b8da23 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py @@ -27,6 +27,9 @@ import os from typing import Optional, Tuple, Union +MACRO_START = "{{" +MACRO_END = "}}" + class TransformCatalog: """ @@ -106,11 +109,17 @@ def json_extract_base_property(path: str, json_col: str, name: str, definition: if "type" not in definition: return None elif is_string(definition["type"]): - return f"cast(json_extract_scalar({json_col}, '{current}') as string) as {name}" + return ( + f"cast({MACRO_START} json_extract_scalar('{json_col}', \"'{current}'\") " + + f"{MACRO_END} as {MACRO_START} dbt_utils.type_string() {MACRO_END}) as {name}" + ) elif is_integer(definition["type"]): - return f"cast(json_extract_scalar({json_col}, '{current}') as int64) as {name}" + return ( + f"cast({MACRO_START} json_extract_scalar('{json_col}', \"'{current}'\") " + + f"{MACRO_END} as {MACRO_START} dbt_utils.type_int() {MACRO_END}) as {name}" + ) elif is_boolean(definition["type"]): - return f"cast(json_extract_scalar({json_col}, '{current}') as boolean) as {name}" + return f"cast({MACRO_START} json_extract_scalar('{json_col}', \"'{current}'\") {MACRO_END} as boolean) as {name}" else: return None @@ -120,9 +129,12 @@ def json_extract_nested_property(path: str, json_col: str, name: str, definition if definition is None or "type" not in definition: return None, None elif is_array(definition["type"]): - return f"json_extract_array({json_col}, '{current}') as {name}", f"cross join unnest({name}) as {name}" + return ( + f"{MACRO_START} json_extract_array('{json_col}', \"'{current}'\") {MACRO_END} as {name}", + f"cross join {MACRO_START} unnest('{name}') {MACRO_END} as {name}", + ) elif is_object(definition["type"]): - return f"json_extract({json_col}, '{current}') as {name}", "" + return f"{MACRO_START} json_extract('{json_col}', \"'{current}'\") {MACRO_END} as {name}", "" else: return None, None @@ -203,25 +215,18 @@ def process_node(path: str, json_col: str, name: str, properties: dict, from_tab prefix = previous + "," node_properties = extract_node_properties(path=path, json_col=json_col, properties=properties) node_columns = ",\n ".join([sql for sql in node_properties.values()]) - # FIXME: use DBT macros to be cross_db compatible instead - hash_node_columns = ( - "coalesce(cast(" - + ' as string), ""),\n coalesce(cast('.join([column for column in node_properties.keys()]) - + ' as string), "")' - ) + hash_node_columns = ", ".join([f'"{column}"' for column in node_properties.keys()]) + hash_node_columns = f"{MACRO_START} dbt_utils.surrogate_key([{hash_node_columns}]) {MACRO_END}" node_sql = f"""{prefix} {name}_node as ( - select - {inject_cols} + select {inject_cols} {node_columns} from {from_table} ), {name}_with_id as ( select *, - to_hex(md5(concat( - {hash_node_columns} - ))) as _{name}_hashid + {hash_node_columns} as _{name}_hashid from {name}_node )""" # SQL Query for current node's basic properties @@ -240,9 +245,7 @@ def process_node(path: str, json_col: str, name: str, properties: dict, from_tab ), {name}_with_id as ( select - to_hex(md5(concat( - {hash_node_columns} - ))) as _{name}_hashid, + {hash_node_columns} as _{name}_hashid, {col} from {name}_node {join_child_table} @@ -255,7 +258,7 @@ def process_node(path: str, json_col: str, name: str, properties: dict, from_tab properties=children_columns[col], from_table=f"{name}_with_id", previous=child_sql, - inject_cols=f"_{name}_hashid as _{name}_foreign_hashid,", + inject_cols=f"\n _{name}_hashid as _{name}_foreign_hashid,", ) result.update(children) else: @@ -284,6 +287,7 @@ def generate_dbt_model(catalog: dict, json_col: str, from_table: str) -> dict: elif "schema" in obj: properties = obj["schema"]["properties"] result.update(process_node(path="$", json_col=json_col, name=name, properties=properties, from_table=from_table)) + # TODO check if jsonpath are expressed similarly on different databases... (using $?) return result From 7693d5ca61ddee93c74967673b9896d52bbf993a Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 4 Nov 2020 18:23:46 +0100 Subject: [PATCH 05/18] Tweaks from review --- .../transform_catalog/transform.py | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py index bb38d08b8da23..0021bb91d8135 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py @@ -50,18 +50,22 @@ def run(self, args): for catalog_file in self.config["catalog"]: print(f"Processing {catalog_file}...") catalog = read_json_catalog(catalog_file) - result = generate_dbt_model(catalog=catalog, json_col=self.config["json"], from_table=self.config["table"]) + result = generate_dbt_model(catalog=catalog, json_col=self.config["json_column"], from_table=self.config["table"]) self.output_sql_models(result) def parse(self, args): parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--catalog", nargs="+", type=str, required=True, help="path to Catalog (JSON Schema) file") parser.add_argument("--out", type=str, required=True, help="path to output generated DBT Models to") - parser.add_argument("--json", type=str, required=True, help="name of the column containing the json blob") + parser.add_argument("--json-column", type=str, required=True, help="name of the column containing the json blob") parser.add_argument("--table", type=str, required=True, help="schema and table name containing the json blob") parsed_args = parser.parse_args(args) - self.config = {"catalog": parsed_args.catalog, "output_path": parsed_args.out, "json": parsed_args.json, "table": parsed_args.table} - print(self.config) + self.config = { + "catalog": parsed_args.catalog, + "output_path": parsed_args.out, + "json_column": parsed_args.json_column, + "table": parsed_args.table, + } def output_sql_models(self, result: dict): output = self.config["output_path"] @@ -69,7 +73,7 @@ def output_sql_models(self, result: dict): if not os.path.exists(output): os.makedirs(output) for file, sql in result.items(): - print(f"Generating {file.lower()}.sql in {output}") + print(f" Generating {file.lower()}.sql in {output}") with open(os.path.join(output, f"{file}.sql").lower(), "w") as f: f.write(sql) @@ -88,6 +92,10 @@ def is_integer(property_type) -> bool: return property_type == "integer" or "integer" in property_type +def is_number(property_type) -> bool: + return property_type == "number" or "number" in property_type + + def is_boolean(property_type) -> bool: return property_type == "boolean" or "boolean" in property_type @@ -118,6 +126,11 @@ def json_extract_base_property(path: str, json_col: str, name: str, definition: f"cast({MACRO_START} json_extract_scalar('{json_col}', \"'{current}'\") " + f"{MACRO_END} as {MACRO_START} dbt_utils.type_int() {MACRO_END}) as {name}" ) + elif is_number(definition["type"]): + return ( + f"cast({MACRO_START} json_extract_scalar('{json_col}', \"'{current}'\") " + + f"{MACRO_END} as {MACRO_START} dbt_utils.type_numeric() {MACRO_END}) as {name}" + ) elif is_boolean(definition["type"]): return f"cast({MACRO_START} json_extract_scalar('{json_col}', \"'{current}'\") {MACRO_END} as boolean) as {name}" else: @@ -276,16 +289,14 @@ def process_node(path: str, json_col: str, name: str, properties: dict, from_tab def generate_dbt_model(catalog: dict, json_col: str, from_table: str) -> dict: result = {} for obj in catalog["streams"]: - name = "undefined" if "name" in obj: name = obj["name"] - elif "stream" in obj: - name = obj["stream"] - properties = {} - if "json_schema" in obj: + else: + name = "undefined" + if "json_schema" in obj and "properties" in obj["json_schema"]: properties = obj["json_schema"]["properties"] - elif "schema" in obj: - properties = obj["schema"]["properties"] + else: + properties = {} result.update(process_node(path="$", json_col=json_col, name=name, properties=properties, from_table=from_table)) # TODO check if jsonpath are expressed similarly on different databases... (using $?) return result From f02d70dc0082ad0a1f8f21ab3b2f7820e100aaea Mon Sep 17 00:00:00 2001 From: cgardens Date: Wed, 4 Nov 2020 14:47:34 -0800 Subject: [PATCH 06/18] turn on basic normalization for data warehouses --- .../destination-bigquery/src/main/resources/spec.json | 6 ++++++ .../destination-postgres/src/main/resources/spec.json | 6 ++++++ .../destination-snowflake/src/main/resources/spec.json | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/airbyte-integrations/connectors/destination-bigquery/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-bigquery/src/main/resources/spec.json index 0ae11658d40c4..82acabeb844f4 100644 --- a/airbyte-integrations/connectors/destination-bigquery/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-bigquery/src/main/resources/spec.json @@ -18,6 +18,12 @@ "credentials_json": { "type": "string", "description": "The contents of the JSON service account key. Check out the docs if you need help generating this key." + }, + "basic_normalization": { + "type": "boolean", + "default": false, + "description": "Whether or not to normalize the data in the destination. See basic normalization for more details.", + "examples": ["false"] } } } diff --git a/airbyte-integrations/connectors/destination-postgres/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-postgres/src/main/resources/spec.json index b44ca23d82eb3..2b0781889ebeb 100644 --- a/airbyte-integrations/connectors/destination-postgres/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-postgres/src/main/resources/spec.json @@ -36,6 +36,12 @@ "type": "string", "examples": ["public"], "default": "public" + }, + "basic_normalization": { + "type": "boolean", + "default": false, + "description": "Whether or not to normalize the data in the destination. See basic normalization for more details.", + "examples": ["false"] } } } diff --git a/airbyte-integrations/connectors/destination-snowflake/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-snowflake/src/main/resources/spec.json index 1c51dcd10e2b5..db8431ae5b519 100644 --- a/airbyte-integrations/connectors/destination-snowflake/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-snowflake/src/main/resources/spec.json @@ -54,6 +54,12 @@ "password": { "description": "Password associated with the username.", "type": "string" + }, + "basic_normalization": { + "type": "boolean", + "default": false, + "description": "Whether or not to normalize the data in the destination. See basic normalization for more details.", + "examples": ["false"] } } } From 8bbb4c934ff06e85945159a158b5f691089c0c23 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 5 Nov 2020 10:26:50 +0100 Subject: [PATCH 07/18] Hook up dbt normalization --- .../bases/base-normalization/entrypoint.sh | 8 +- .../integration_tests/catalog.json | 94 +++++-------------- .../integration_tests/one_recipe.json | 74 ++++++++++++++- .../transform_catalog/transform.py | 2 +- 4 files changed, 99 insertions(+), 79 deletions(-) diff --git a/airbyte-integrations/bases/base-normalization/entrypoint.sh b/airbyte-integrations/bases/base-normalization/entrypoint.sh index 8df4cdb26db55..94287ad4c13a8 100755 --- a/airbyte-integrations/bases/base-normalization/entrypoint.sh +++ b/airbyte-integrations/bases/base-normalization/entrypoint.sh @@ -44,11 +44,9 @@ function main() { case "$CMD" in run) transform-config --config "$CONFIG_FILE" --integration-type "$INTEGRATION_TYPE" --out "$DBT_PROFILE" - # todo (cgardens) - @ChristopheDuong adjust these args if necessary. - transform-catalog --catalog "$CATALOG_FILE" --integration-type "$INTEGRATION_TYPE" --out "$DBT_MODEL" - - # todo (cgardens) - @ChristopheDuong this is my best guess at how we are supposed to invoke dbt. adjust when they are inevitably not quite right. - dbt run --profiles-dir $(pwd) --project-dir $(pwd) --full-refresh --fail-fast + transform-catalog --catalog "$CATALOG_FILE" --out "$DBT_MODEL" --json-column data --table "???" + dbt deps --profiles-dir $(pwd) --project-dir $(pwd) + dbt run --profiles-dir $(pwd) --project-dir $(pwd) ;; dry-run) error "Not Implemented" diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json b/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json index 7cb3dd59836db..dbf4086716e6e 100644 --- a/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json +++ b/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json @@ -2,8 +2,7 @@ "streams": [ { "name": "one_recipe", - "json_schema": - { + "json_schema": { "$schema": "http://json-schema.org/draft-07/schema", "$id": "http://example.com/example.json", "type": "object", @@ -135,9 +134,7 @@ "title": "The Name schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "Christmas pie" - ] + "examples": ["Christmas pie"] }, "url": { "$id": "#/properties/url", @@ -165,9 +162,7 @@ "title": "The Author schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "Mary Cadogan" - ] + "examples": ["Mary Cadogan"] }, "Ingredients": { "$id": "#/properties/Ingredients", @@ -215,11 +210,7 @@ } } ], - "required": [ - "quantity", - "ingredient", - "nutritionfacts" - ], + "required": ["quantity", "ingredient", "nutritionfacts"], "properties": { "quantity": { "$id": "#/properties/Ingredients/items/anyOf/0/properties/quantity", @@ -227,9 +218,7 @@ "title": "The quantity schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "2 tbsp" - ] + "examples": ["2 tbsp"] }, "ingredient": { "$id": "#/properties/Ingredients/items/anyOf/0/properties/ingredient", @@ -237,9 +226,7 @@ "title": "The ingredient schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "olive oil" - ] + "examples": ["olive oil"] }, "nutritionfacts": { "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts", @@ -253,10 +240,7 @@ "fat": "13.5g" } ], - "required": [ - "calories", - "fat" - ], + "required": ["calories", "fat"], "properties": { "calories": { "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/calories", @@ -264,9 +248,7 @@ "title": "The calories schema", "description": "An explanation about the purpose of this instance.", "default": 0, - "examples": [ - 119 - ] + "examples": [119] }, "fat": { "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/fat", @@ -274,9 +256,7 @@ "title": "The fat schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "13.5g" - ] + "examples": ["13.5g"] } }, "additionalProperties": true @@ -314,9 +294,7 @@ "title": "The quantity schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "1" - ] + "examples": ["1"] }, "ingredient": { "$id": "#/properties/Ingredients/items/anyOf/1/properties/ingredient", @@ -324,9 +302,7 @@ "title": "The ingredient schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "onion" - ] + "examples": ["onion"] }, "preparation": { "$id": "#/properties/Ingredients/items/anyOf/1/properties/preparation", @@ -334,9 +310,7 @@ "title": "The preparation schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "finely chopped" - ] + "examples": ["finely chopped"] }, "nutritionfacts": { "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts", @@ -350,10 +324,7 @@ "fat": "11.5g" } ], - "required": [ - "calories", - "fat" - ], + "required": ["calories", "fat"], "properties": { "calories": { "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/calories", @@ -361,9 +332,7 @@ "title": "The calories schema", "description": "An explanation about the purpose of this instance.", "default": 0, - "examples": [ - 102 - ] + "examples": [102] }, "fat": { "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/fat", @@ -371,9 +340,7 @@ "title": "The fat schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "11.5g" - ] + "examples": ["11.5g"] } }, "additionalProperties": true @@ -393,10 +360,7 @@ "ingredient": "fresh white breadcrumbs" } ], - "required": [ - "quantity", - "ingredient" - ], + "required": ["quantity", "ingredient"], "properties": { "quantity": { "$id": "#/properties/Ingredients/items/anyOf/2/properties/quantity", @@ -404,9 +368,7 @@ "title": "The quantity schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "100g" - ] + "examples": ["100g"] }, "ingredient": { "$id": "#/properties/Ingredients/items/anyOf/2/properties/ingredient", @@ -414,9 +376,7 @@ "title": "The ingredient schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "fresh white breadcrumbs" - ] + "examples": ["fresh white breadcrumbs"] } }, "additionalProperties": true @@ -434,11 +394,7 @@ "preparation": "chopped" } ], - "required": [ - "quantity", - "ingredient", - "preparation" - ], + "required": ["quantity", "ingredient", "preparation"], "properties": { "quantity": { "$id": "#/properties/Ingredients/items/anyOf/3/properties/quantity", @@ -446,9 +402,7 @@ "title": "The quantity schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "2 tsp" - ] + "examples": ["2 tsp"] }, "ingredient": { "$id": "#/properties/Ingredients/items/anyOf/3/properties/ingredient", @@ -456,9 +410,7 @@ "title": "The ingredient schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "fresh or dried thyme" - ] + "examples": ["fresh or dried thyme"] }, "preparation": { "$id": "#/properties/Ingredients/items/anyOf/3/properties/preparation", @@ -466,9 +418,7 @@ "title": "The preparation schema", "description": "An explanation about the purpose of this instance.", "default": "", - "examples": [ - "chopped" - ] + "examples": ["chopped"] } }, "additionalProperties": true diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/one_recipe.json b/airbyte-integrations/bases/base-normalization/integration_tests/one_recipe.json index 46be0837acd14..efcba70869d5e 100644 --- a/airbyte-integrations/bases/base-normalization/integration_tests/one_recipe.json +++ b/airbyte-integrations/bases/base-normalization/integration_tests/one_recipe.json @@ -1 +1,73 @@ -{ "Name": "Christmas pie", "url": "https://www.bbcgoodfood.com/recipes/2793/christmas-pie", "Description": "Combine a few key Christmas flavours here to make a pie that both children and adults will adore", "Author": "Mary Cadogan", "Ingredients": [ { "quantity": "2 tbsp", "ingredient": "olive oil", "nutritionfacts": { "calories": 119, "fat": "13.5g" } }, { "quantity": "knob", "ingredient": "butter", "nutritionfacts": { "calories": 102, "fat": "11.5g" } }, { "quantity": "1", "ingredient": "onion", "preparation": "finely chopped", "nutritionfacts": { "calories": 102, "fat": "11.5g" } }, { "quantity": "500g", "ingredient": "sausagemeat or skinned sausages", "nutritionfacts": { "calories": 268, "fat": "18g" } }, { "quantity": "1", "ingredient": "lemon", "preparation": "grated zest", "nutritionfacts": { "calories": 13 } }, { "quantity": "100g", "ingredient": "fresh white breadcrumbs" }, { "quantity": "85g", "ingredient": "ready-to-eat dried apricots", "preparation": "chopped", "nutritionfacts": { "calories": 34, "fat": "0.27g" } }, { "quantity": "58g", "ingredient": "chestnut, canned or vacuum-packed", "preparation": "chopped", "nutritionfacts": { "calories": 77, "fat": "1g" } }, { "quantity": "2 tsp", "ingredient": "fresh or dried thyme", "preparation": "chopped" }, { "quantity": "100g", "ingredient": "cranberries, fresh or frozen" }, { "quantity": "500g", "ingredient": "boneless, skinless chicken breasts", "nutritionfacts": { "calories": 284, "fat": "6.2g" } }, { "quantity": "500g", "ingredient": "pack ready-made shortcrust pastry" }, { "quantity": "1", "ingredient": "beaten egg", "preparation": "to glaze", "nutritionfacts": { "calories": 75, "fat": "5g" } } ], "Method": [ "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.", "Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.", "Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.", "Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles." ]} \ No newline at end of file +{ + "Name": "Christmas pie", + "url": "https://www.bbcgoodfood.com/recipes/2793/christmas-pie", + "Description": "Combine a few key Christmas flavours here to make a pie that both children and adults will adore", + "Author": "Mary Cadogan", + "Ingredients": [ + { + "quantity": "2 tbsp", + "ingredient": "olive oil", + "nutritionfacts": { "calories": 119, "fat": "13.5g" } + }, + { + "quantity": "knob", + "ingredient": "butter", + "nutritionfacts": { "calories": 102, "fat": "11.5g" } + }, + { + "quantity": "1", + "ingredient": "onion", + "preparation": "finely chopped", + "nutritionfacts": { "calories": 102, "fat": "11.5g" } + }, + { + "quantity": "500g", + "ingredient": "sausagemeat or skinned sausages", + "nutritionfacts": { "calories": 268, "fat": "18g" } + }, + { + "quantity": "1", + "ingredient": "lemon", + "preparation": "grated zest", + "nutritionfacts": { "calories": 13 } + }, + { "quantity": "100g", "ingredient": "fresh white breadcrumbs" }, + { + "quantity": "85g", + "ingredient": "ready-to-eat dried apricots", + "preparation": "chopped", + "nutritionfacts": { "calories": 34, "fat": "0.27g" } + }, + { + "quantity": "58g", + "ingredient": "chestnut, canned or vacuum-packed", + "preparation": "chopped", + "nutritionfacts": { "calories": 77, "fat": "1g" } + }, + { + "quantity": "2 tsp", + "ingredient": "fresh or dried thyme", + "preparation": "chopped" + }, + { "quantity": "100g", "ingredient": "cranberries, fresh or frozen" }, + { + "quantity": "500g", + "ingredient": "boneless, skinless chicken breasts", + "nutritionfacts": { "calories": 284, "fat": "6.2g" } + }, + { "quantity": "500g", "ingredient": "pack ready-made shortcrust pastry" }, + { + "quantity": "1", + "ingredient": "beaten egg", + "preparation": "to glaze", + "nutritionfacts": { "calories": 75, "fat": "5g" } + } + ], + "Method": [ + "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", + "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.", + "Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.", + "Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.", + "Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles." + ] +} diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py index 0021bb91d8135..128cbe1512ff5 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py @@ -38,7 +38,7 @@ class TransformCatalog: python3 main_dev_transform_catalog.py \ --catalog integration_tests/catalog.json \ --out normalization/dbt-transform/models/generated/ \ - --json json_blob \ + --json-column json_blob \ --table airbytesandbox.data.one_recipe_json ``` """ From 9a2c8c9a8adecee99a84f10b7104097bbc99a8ed Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 5 Nov 2020 16:20:19 +0100 Subject: [PATCH 08/18] Connect normalization pieces together --- .gitignore | 7 ++- .../bases/base-normalization/.dockerignore | 1 + .../bases/base-normalization/Dockerfile | 3 + .../README.md | 0 .../dbt_project.yml | 5 +- .../macros/cross_db_utils/array.sql | 0 .../macros/cross_db_utils/json_operations.sql | 0 .../packages.yml | 0 .../bases/base-normalization/entrypoint.sh | 22 ++++--- .../integration_tests/catalog.json | 2 +- .../dbt-transform/models/sources.yml | 7 --- .../transform_catalog/transform.py | 60 ++++++++++++++----- .../transform_config/transform.py | 9 ++- .../bases/base-normalization/setup.py | 2 +- 14 files changed, 76 insertions(+), 42 deletions(-) rename airbyte-integrations/bases/base-normalization/{normalization/dbt-transform => dbt-project-template}/README.md (100%) rename airbyte-integrations/bases/base-normalization/{normalization/dbt-transform => dbt-project-template}/dbt_project.yml (88%) rename airbyte-integrations/bases/base-normalization/{normalization/dbt-transform => dbt-project-template}/macros/cross_db_utils/array.sql (100%) rename airbyte-integrations/bases/base-normalization/{normalization/dbt-transform => dbt-project-template}/macros/cross_db_utils/json_operations.sql (100%) rename airbyte-integrations/bases/base-normalization/{normalization/dbt-transform => dbt-project-template}/packages.yml (100%) delete mode 100644 airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/sources.yml diff --git a/.gitignore b/.gitignore index 665e0a1abba22..ad7a98c6dfc37 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ __pycache__ .mypy_cache # dbt -airbyte-integrations/bases/base-normalization/normalization/dbt-transform/logs/ -airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/generated -airbyte-integrations/bases/base-normalization/normalization/dbt-transform/dbt_modules/ +profiles.yml +airbyte-integrations/bases/base-normalization/dbt-project-template/logs/ +airbyte-integrations/bases/base-normalization/dbt-project-template/models/generated +airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_modules/ diff --git a/airbyte-integrations/bases/base-normalization/.dockerignore b/airbyte-integrations/bases/base-normalization/.dockerignore index 2867a55ca37e4..c1b470f707044 100644 --- a/airbyte-integrations/bases/base-normalization/.dockerignore +++ b/airbyte-integrations/bases/base-normalization/.dockerignore @@ -3,3 +3,4 @@ !entrypoint.sh !setup.py !normalization +!dbt-project-template diff --git a/airbyte-integrations/bases/base-normalization/Dockerfile b/airbyte-integrations/bases/base-normalization/Dockerfile index 72947277f41aa..cf6a9b190e3ea 100644 --- a/airbyte-integrations/bases/base-normalization/Dockerfile +++ b/airbyte-integrations/bases/base-normalization/Dockerfile @@ -10,6 +10,9 @@ WORKDIR /airbyte/normalization_code COPY normalization ./normalization COPY setup.py . RUN pip install . +COPY dbt-project-template/macros ./dbt-template/macros +COPY dbt-project-template/dbt_project.yml ./dbt-template/dbt_project.yml +COPY dbt-project-template/packages.yml ./dbt-template/packages.yml WORKDIR /airbyte diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/README.md b/airbyte-integrations/bases/base-normalization/dbt-project-template/README.md similarity index 100% rename from airbyte-integrations/bases/base-normalization/normalization/dbt-transform/README.md rename to airbyte-integrations/bases/base-normalization/dbt-project-template/README.md diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/dbt_project.yml b/airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_project.yml similarity index 88% rename from airbyte-integrations/bases/base-normalization/normalization/dbt-transform/dbt_project.yml rename to airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_project.yml index 1edbf53543b16..8e45744722a5b 100755 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/dbt_project.yml +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_project.yml @@ -7,7 +7,7 @@ config-version: 2 # This setting configures which "profile" dbt uses for this project. Profiles contain # database connection information, and should be configured in the ~/.dbt/profiles.yml file -profile: 'dev' +profile: 'normalize' # These configurations specify where dbt should look for different types of files. # The `source-paths` config, for example, states that source models can be found @@ -29,5 +29,6 @@ clean-targets: # directories to be removed by `dbt clean` # are materialized, and more! models: airbyte: - +schema: normalization + # Schema (dataset) defined in profiles.yml is concatenated with schema below for dbt's final output + +schema: normalized +materialized: view \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/macros/cross_db_utils/array.sql b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql similarity index 100% rename from airbyte-integrations/bases/base-normalization/normalization/dbt-transform/macros/cross_db_utils/array.sql rename to airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/macros/cross_db_utils/json_operations.sql b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql similarity index 100% rename from airbyte-integrations/bases/base-normalization/normalization/dbt-transform/macros/cross_db_utils/json_operations.sql rename to airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/packages.yml b/airbyte-integrations/bases/base-normalization/dbt-project-template/packages.yml similarity index 100% rename from airbyte-integrations/bases/base-normalization/normalization/dbt-transform/packages.yml rename to airbyte-integrations/bases/base-normalization/dbt-project-template/packages.yml diff --git a/airbyte-integrations/bases/base-normalization/entrypoint.sh b/airbyte-integrations/bases/base-normalization/entrypoint.sh index 94287ad4c13a8..c0d51ebb10651 100755 --- a/airbyte-integrations/bases/base-normalization/entrypoint.sh +++ b/airbyte-integrations/bases/base-normalization/entrypoint.sh @@ -2,10 +2,6 @@ set -e -# dbt looks specifically for files named profiles.yml and dbt_project.yml -DBT_PROFILE=profiles.yml -DBT_MODEL=dbt_project.yml - function echo2() { echo >&2 "$@" } @@ -15,6 +11,8 @@ function error() { exit 1 } +PROJECT_DIR=$(pwd) + ## todo: make it easy to select source or destination and validate based on selection by adding an integration type env variable. function main() { CMD="$1" @@ -43,13 +41,19 @@ function main() { case "$CMD" in run) - transform-config --config "$CONFIG_FILE" --integration-type "$INTEGRATION_TYPE" --out "$DBT_PROFILE" - transform-catalog --catalog "$CATALOG_FILE" --out "$DBT_MODEL" --json-column data --table "???" - dbt deps --profiles-dir $(pwd) --project-dir $(pwd) - dbt run --profiles-dir $(pwd) --project-dir $(pwd) + cp -r /airbyte/normalization_code/dbt-template/* $PROJECT_DIR + transform-config --config "$CONFIG_FILE" --integration-type "$INTEGRATION_TYPE" --out $PROJECT_DIR + transform-catalog --profile-config-dir $PROJECT_DIR --catalog "$CATALOG_FILE" --out $PROJECT_DIR/models/generated/ --json-column data + dbt deps --profiles-dir $PROJECT_DIR --project-dir $PROJECT_DIR + dbt run --profiles-dir $PROJECT_DIR --project-dir $PROJECT_DIR ;; dry-run) - error "Not Implemented" + cp -r /airbyte/normalization_code/dbt-template/* $PROJECT_DIR + transform-config --config "$CONFIG_FILE" --integration-type "$INTEGRATION_TYPE" --out $PROJECT_DIR + dbt debug --profiles-dir $PROJECT_DIR --project-dir $PROJECT_DIR + transform-catalog --profile-config-dir $PROJECT_DIR --catalog "$CATALOG_FILE" --out $PROJECT_DIR/models/generated/ --json-column data + dbt deps --profiles-dir $PROJECT_DIR --project-dir $PROJECT_DIR + dbt compile --profiles-dir $PROJECT_DIR --project-dir $PROJECT_DIR ;; *) error "Unknown command: $CMD" diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json b/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json index dbf4086716e6e..6a2eee4856e6c 100644 --- a/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json +++ b/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json @@ -1,7 +1,7 @@ { "streams": [ { - "name": "one_recipe", + "name": "recipes_json", "json_schema": { "$schema": "http://json-schema.org/draft-07/schema", "$id": "http://example.com/example.json", diff --git a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/sources.yml b/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/sources.yml deleted file mode 100644 index 1277e01279f6c..0000000000000 --- a/airbyte-integrations/bases/base-normalization/normalization/dbt-transform/models/sources.yml +++ /dev/null @@ -1,7 +0,0 @@ -version: 2 - -sources: - - name: data - tables: - - name: recipes - - name: recipes_json \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py index 128cbe1512ff5..590243bdc2ad2 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py @@ -25,7 +25,9 @@ import argparse import json import os -from typing import Optional, Tuple, Union +from typing import Optional, Set, Tuple, Union + +import yaml MACRO_START = "{{" MACRO_END = "}}" @@ -36,10 +38,10 @@ class TransformCatalog: To run this transformation: ``` python3 main_dev_transform_catalog.py \ + --profile-config-dir . \ --catalog integration_tests/catalog.json \ - --out normalization/dbt-transform/models/generated/ \ - --json-column json_blob \ - --table airbytesandbox.data.one_recipe_json + --out dir \ + --json-column json_blob ``` """ @@ -47,28 +49,36 @@ class TransformCatalog: def run(self, args): self.parse(args) - for catalog_file in self.config["catalog"]: - print(f"Processing {catalog_file}...") - catalog = read_json_catalog(catalog_file) - result = generate_dbt_model(catalog=catalog, json_col=self.config["json_column"], from_table=self.config["table"]) - self.output_sql_models(result) + self.process_catalog() def parse(self, args): parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("--profile-config-dir", type=str, required=True, help="path to directory containing DBT profiles.yml") parser.add_argument("--catalog", nargs="+", type=str, required=True, help="path to Catalog (JSON Schema) file") parser.add_argument("--out", type=str, required=True, help="path to output generated DBT Models to") - parser.add_argument("--json-column", type=str, required=True, help="name of the column containing the json blob") - parser.add_argument("--table", type=str, required=True, help="schema and table name containing the json blob") + parser.add_argument("--json-column", type=str, required=False, help="name of the column containing the json blob") parsed_args = parser.parse_args(args) self.config = { + "schema": extract_schema(parsed_args.profile_config_dir), "catalog": parsed_args.catalog, "output_path": parsed_args.out, "json_column": parsed_args.json_column, - "table": parsed_args.table, } - def output_sql_models(self, result: dict): + def process_catalog(self): + source_tables: set = set() + schema = self.config["schema"] output = self.config["output_path"] + for catalog_file in self.config["catalog"]: + print(f"Processing {catalog_file}...") + catalog = read_json_catalog(catalog_file) + result, tables = generate_dbt_model(catalog=catalog, json_col=self.config["json_column"], schema=schema) + self.output_sql_models(output, result) + source_tables = source_tables.union(tables) + self.write_yaml_sources(output, schema, source_tables) + + @staticmethod + def output_sql_models(output: str, result: dict) -> None: if result: if not os.path.exists(output): os.makedirs(output) @@ -77,6 +87,21 @@ def output_sql_models(self, result: dict): with open(os.path.join(output, f"{file}.sql").lower(), "w") as f: f.write(sql) + @staticmethod + def write_yaml_sources(output: str, schema: str, sources: set) -> None: + tables = [{"name": source} for source in sources] + source_config = {"version": 2, "sources": [{"name": schema, "tables": tables}]} + source_path = os.path.join(output, "sources.yml") + if not os.path.exists(source_path): + with open(source_path, "w") as fh: + fh.write(yaml.dump(source_config)) + + +def extract_schema(profile_dir: str): + with open(os.path.join(profile_dir, "profiles.yml"), "r") as file: + config = yaml.load(file, Loader=yaml.FullLoader) + return config["normalize"]["outputs"]["prod"]["dataset"] + def read_json_catalog(input_path: str) -> dict: with open(input_path, "r") as file: @@ -286,8 +311,9 @@ def process_node(path: str, json_col: str, name: str, properties: dict, from_tab return result -def generate_dbt_model(catalog: dict, json_col: str, from_table: str) -> dict: +def generate_dbt_model(catalog: dict, json_col: str, schema: str) -> Tuple[dict, Set[Union[str]]]: result = {} + source_tables = set() for obj in catalog["streams"]: if "name" in obj: name = obj["name"] @@ -297,9 +323,11 @@ def generate_dbt_model(catalog: dict, json_col: str, from_table: str) -> dict: properties = obj["json_schema"]["properties"] else: properties = {} - result.update(process_node(path="$", json_col=json_col, name=name, properties=properties, from_table=from_table)) + table = f"{MACRO_START} source('{schema}','{name}') {MACRO_END}" # TODO check if jsonpath are expressed similarly on different databases... (using $?) - return result + result.update(process_node(path="$", json_col=json_col, name=name, properties=properties, from_table=table)) + source_tables.add(name) + return result, source_tables def main(args=None): diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_config/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_config/transform.py index 4bdef927632aa..41f5e3d615baa 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_config/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_config/transform.py @@ -24,6 +24,7 @@ import argparse import json +import os import pkgutil from enum import Enum @@ -40,8 +41,8 @@ class TransformConfig: def run(self, args): inputs = self.parse(args) original_config = self.read_json_config(inputs["config"]) - transformed_config = self.transform(inputs["integration_type"], original_config) - + integration_type = inputs["integration_type"] + transformed_config = self.transform(integration_type, original_config) self.write_yaml_config(inputs["output_path"], transformed_config) def parse(self, args): @@ -138,7 +139,9 @@ def read_json_config(self, input_path: str): return json.loads(contents) def write_yaml_config(self, output_path: str, config: dict): - with open(output_path, "w") as fh: + if not os.path.exists(output_path): + os.makedirs(output_path) + with open(os.path.join(output_path, "profiles.yml"), "w") as fh: fh.write(yaml.dump(config)) diff --git a/airbyte-integrations/bases/base-normalization/setup.py b/airbyte-integrations/bases/base-normalization/setup.py index da2328b575425..a8ac4d001789e 100644 --- a/airbyte-integrations/bases/base-normalization/setup.py +++ b/airbyte-integrations/bases/base-normalization/setup.py @@ -31,7 +31,7 @@ author_email="contact@airbyte.io", url="https://github.com/airbytehq/airbyte", packages=setuptools.find_packages(), - install_requires=["airbyte-protocol", "dbt==0.18.1"], + install_requires=["airbyte-protocol", "dbt>=0.18.1", "pyyaml"], package_data={"": ["*.yml"]}, setup_requires=["pytest-runner"], tests_require=["pytest"], From a44b52c32981df6646b5267e2ff81992b5e03cbb Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 5 Nov 2020 18:40:27 +0100 Subject: [PATCH 09/18] Exclude spotless from DBT folders --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9289fd8cd60c2..55a865911806e 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,7 @@ spotless { } sql { target '**/*.sql' - targetExclude "**/build/**/*", "**/.gradle/**/*", "**/.venv/**" + targetExclude "**/build/**/*", "**/.gradle/**/*", "**/.venv/**", "airbyte-integrations/bases/base-normalization/dbt-project-template/**/*" dbeaver().configFile(rootProject.file('tools/gradle/codestyle/sql-dbeaver.properties')) } From 1ad010a7979b56c43f165d014064b126cfe86827 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 5 Nov 2020 22:42:24 +0100 Subject: [PATCH 10/18] Add placeholder adapter functions for postgres flavor of sql --- .../macros/cross_db_utils/array.sql | 4 ++++ .../macros/cross_db_utils/json_operations.sql | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql index 482053eab2f60..b02d80994e161 100644 --- a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql @@ -19,6 +19,10 @@ unnest({{ array_col }}) {%- endmacro %} +{% macro postgres__unnest(array_col) -%} + unnest({{ array_col }}) +{%- endmacro %} + {% macro redshift__unnest(array_col) -%} -- FIXME to implement as described here? https://blog.getdbt.com/how-to-unnest-arrays-in-redshift/ {%- endmacro %} diff --git a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql index f3ab19731979e..ef7797c92faef 100644 --- a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql @@ -19,6 +19,10 @@ json_extract({{json_column}}, {{json_path}}) {%- endmacro %} +{% macro postgres__json_extract(json_column, json_path) -%} + json_extract_path_text({{json_column}}, {{json_path}}) +{%- endmacro %} + {% macro redshift__json_extract(json_column, json_path) -%} json_extract_path_text({{json_column}}, {{json_path}}) {%- endmacro %} @@ -41,6 +45,10 @@ json_extract_scalar({{json_column}}, {{json_path}}) {%- endmacro %} +{% macro postgres__json_extract_scalar(json_column, json_path) -%} + json_extract_path_text({{json_column}}, {{json_path}}) +{%- endmacro %} + {% macro redshift__json_extract_scalar(json_column, json_path) -%} json_extract_path_text({{json_column}}, {{json_path}}) {%- endmacro %} @@ -63,6 +71,10 @@ json_extract_array({{json_column}}, {{json_path}}) {%- endmacro %} +{% macro postgres__json_extract_array(json_column, json_path) -%} + json_array_elements(json_extract_path({{json_column}}, {{json_path}})) +{%- endmacro %} + {% macro redshift__json_extract_array(json_column, json_path) -%} json_extract_path_text({{json_column}}, {{json_path}}) {%- endmacro %} From 08b063c9ebf28b87857763d2b41907f0afea2a82 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 6 Nov 2020 00:44:16 +0100 Subject: [PATCH 11/18] Correct some snowflakes specifities --- .../base-normalization/dbt-project-template/README.md | 10 +++++++--- .../dbt-project-template/dbt_project.yml | 8 +++++++- .../normalization/transform_catalog/transform.py | 6 +++++- .../normalization/transform_config/transform.py | 4 ++-- .../unit_tests/test_transform_config.py | 4 ++-- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/airbyte-integrations/bases/base-normalization/dbt-project-template/README.md b/airbyte-integrations/bases/base-normalization/dbt-project-template/README.md index 4336065f3ab23..2d1c3f7fbc74a 100644 --- a/airbyte-integrations/bases/base-normalization/dbt-project-template/README.md +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/README.md @@ -10,6 +10,10 @@ 1. You can now run DBT commands, to check the setup is fine: `dbt debug` 1. To build the DBT tables in your warehouse: `dbt run` -Note that in order to work with the current models that i am testing, you should have: - - `recipes` and `recipes_json` tables - - in a `data` dataset in your bigquery project (referenced in your `profiles.yml`... \ No newline at end of file +## Running DBT from Airbyte generated config + +1. You can also change directory (`cd /tmp/dev_root/workspace/1/0/normalize` for example) to one of the workspace generated by Airbyte within one of the `normalize` folder. +1. You should find `profiles.yml` and a bunch of other DBT files/folders created there. +1. To check everything is setup properly: `dbt debug --profiles-dir=$(pwd) --project-dir=$(pwd)` +1. You can modify the `.sql` files and run `dbt run --profiles-dir=$(pwd) --project-dir=$(pwd)` too +1. You can inspect compiled DBT `.sql` files before they are run in the destination engine in `normalize/build/compiled` or `normalize/build/run` folders \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_project.yml b/airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_project.yml index 8e45744722a5b..9a1586c77621e 100755 --- a/airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_project.yml +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_project.yml @@ -31,4 +31,10 @@ models: airbyte: # Schema (dataset) defined in profiles.yml is concatenated with schema below for dbt's final output +schema: normalized - +materialized: view \ No newline at end of file + +materialized: view + +# https://docs.getdbt.com/reference/project-configs/quoting/ +quoting: + database: true + schema: true + identifier: true diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py index 590243bdc2ad2..517c7d14f538c 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py @@ -100,7 +100,11 @@ def write_yaml_sources(output: str, schema: str, sources: set) -> None: def extract_schema(profile_dir: str): with open(os.path.join(profile_dir, "profiles.yml"), "r") as file: config = yaml.load(file, Loader=yaml.FullLoader) - return config["normalize"]["outputs"]["prod"]["dataset"] + obj = config["normalize"]["outputs"]["prod"] + if "dataset" in obj: + return obj["dataset"] + else: + return obj["schema"] def read_json_catalog(input_path: str) -> dict: diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_config/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_config/transform.py index 41f5e3d615baa..cee8643a1d4f7 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_config/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_config/transform.py @@ -119,8 +119,8 @@ def transform_snowflake(self, config: dict): # https://docs.getdbt.com/reference/warehouse-profiles/snowflake-profile dbt_config["type"] = "snowflake" - # account is the first term subdomain of the host. - dbt_config["account"] = config["host"].split(".")[0] + # here account is everything before ".snowflakecomputing.com" as it can include account, region & cloud environment information) + dbt_config["account"] = config["host"].replace(".snowflakecomputing.com", "") dbt_config["user"] = config["username"] dbt_config["password"] = config["password"] dbt_config["role"] = config["role"] diff --git a/airbyte-integrations/bases/base-normalization/unit_tests/test_transform_config.py b/airbyte-integrations/bases/base-normalization/unit_tests/test_transform_config.py index 68d9373f8e01d..d1da94d2b7282 100644 --- a/airbyte-integrations/bases/base-normalization/unit_tests/test_transform_config.py +++ b/airbyte-integrations/bases/base-normalization/unit_tests/test_transform_config.py @@ -81,7 +81,7 @@ def test_transform_postgres(self): def test_transform_snowflake(self): input = { - "host": "123.airbyte.io", + "host": "123abc.us-east-7.aws.snowflakecomputing.com", "role": "AIRBYTE_ROLE", "warehouse": "AIRBYTE_WAREHOUSE", "database": "AIRBYTE_DATABASE", @@ -92,7 +92,7 @@ def test_transform_snowflake(self): actual = TransformConfig().transform_snowflake(input) expected = { - "account": "123", + "account": "123abc.us-east-7.aws", "client_session_keep_alive": False, "database": "AIRBYTE_DATABASE", "password": "password123", From c9c6fe4b6f7e2344a5f0c8a3e5762ba06e68c874 Mon Sep 17 00:00:00 2001 From: Sherif Nada Date: Thu, 5 Nov 2020 22:35:43 -0800 Subject: [PATCH 12/18] pull from master --- .github/workflows/gradle.yml | 1 + README.md | 6 +- airbyte-api/src/main/openapi/config.yaml | 3 + .../connector-templates/generator/plopfile.js | 33 +++++++-- .../.secrets/credentials.json.hbs | 4 -- .../source-python/Dockerfile.test.hbs | 4 +- .../source-python/NEW_SOURCE_CHECKLIST.md.hbs | 16 ++++- .../source-python/README.md.hbs | 46 ++++++------ .../source-python/setup.py.hbs | 2 +- .../.secrets/credentials.json.hbs | 4 -- .../source-singer/Dockerfile.test.hbs | 4 +- .../source-singer/NEW_SOURCE_CHECKLIST.md.hbs | 18 +++-- .../source-singer/README.md.hbs | 45 ++++++------ .../source-singer/setup.py.hbs | 2 +- .../airbyte/server/apis/ConfigurationApi.java | 2 +- .../WebBackendConnectionsHandler.java | 11 ++- .../WebBackendConnectionsHandlerTest.java | 21 +++++- build.gradle | 72 +++++++++---------- docs/SUMMARY.md | 1 + docs/architecture/README.md | 2 +- docs/changelog.md | 8 +-- docs/deploying-airbyte/on-aws-ec2.md | 10 +-- .../on-gcp-compute-engine.md | 4 +- docs/getting-started-tutorial.md | 8 +-- docs/integrations/destinations/snowflake.md | 13 ++-- docs/integrations/integrations-changelog.md | 9 ++- .../sources/facebook-marketing-api.md | 28 ++++---- docs/integrations/sources/google-adwords.md | 8 ++- docs/integrations/sources/google-sheets.md | 2 +- tools/bin/ci_credentials.sh | 4 ++ tools/bin/standard_test_pr.sh | 3 +- 31 files changed, 229 insertions(+), 165 deletions(-) delete mode 100644 airbyte-integrations/connector-templates/source-python/.secrets/credentials.json.hbs delete mode 100644 airbyte-integrations/connector-templates/source-singer/.secrets/credentials.json.hbs diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index b7d855b9c5802..6a38660c6fd90 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -54,6 +54,7 @@ jobs: SNOWFLAKE_INTEGRATION_TEST_CREDS: ${{ secrets.SNOWFLAKE_INTEGRATION_TEST_CREDS }} ADWORDS_INTEGRATION_TEST_CREDS: ${{ secrets.ADWORDS_INTEGRATION_TEST_CREDS }} FACEBOOK_MARKETING_API_TEST_INTEGRATION_CREDS: ${{ secrets.FACEBOOK_MARKETING_API_TEST_INTEGRATION_CREDS }} + SOURCE_MARKETO_SINGER_INTEGRATION_TEST_CONFIG: ${{ secrets.SOURCE_MARKETO_SINGER_INTEGRATION_TEST_CONFIG }} - name: Build run: ./gradlew --no-daemon build --scan diff --git a/README.md b/README.md index 823415a2bd92d..fb3f4d521d3f0 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/airbytehq/airbyte/Airbyte%20CI) ![License](https://img.shields.io/github/license/airbytehq/airbyte) -![](docs/.gitbook/assets/airbyte_horizontal_dark.svg) +![](docs/.gitbook/assets/airbyte_horizontal_dark%20%281%29.svg) ### Data integration made simple, secure and extensible. The new open-source standard to sync data from applications, APIs & databases to warehouses. -[![](docs/.gitbook/assets/deploy-locally%20%281%29%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29%20%281%29%20%281%29.svg)](docs/deploying-airbyte/on-your-workstation.md) [![](docs/.gitbook/assets/deploy-on-aws%20%281%29%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29%20%281%29%20%281%29.svg)](docs/deploying-airbyte/on-aws-ec2.md) [![](docs/.gitbook/assets/deploy-on-gcp%20%281%29%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29%20%281%29%20%281%29.svg)](docs/deploying-airbyte/on-gcp-compute-engine.md) +[![](docs/.gitbook/assets/deploy-locally%20%281%29%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29%20%281%29%20%281%29%20%282%29.svg)](docs/deploying-airbyte/on-your-workstation.md) [![](docs/.gitbook/assets/deploy-on-aws%20%281%29%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29%20%281%29%20%281%29%20%282%29.svg)](docs/deploying-airbyte/on-aws-ec2.md) [![](docs/.gitbook/assets/deploy-on-gcp%20%281%29%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29%20%281%29%20%281%29%20%282%29.svg)](docs/deploying-airbyte/on-gcp-compute-engine.md) -![](docs/.gitbook/assets/airbyte-ui-for-your-integration-pipelines.png) +![](docs/.gitbook/assets/airbyte-ui-for-your-integration-pipelines%20%281%29.png) Airbyte is on a mission to make data integration pipelines a commodity. diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index f7aaa9903989e..deeefa5bc87ba 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -1598,6 +1598,7 @@ components: - syncSchema - status - source + - destination - isSyncing properties: connectionId: @@ -1620,6 +1621,8 @@ components: $ref: "#/components/schemas/ConnectionStatus" source: $ref: "#/components/schemas/SourceRead" + destination: + $ref: "#/components/schemas/DestinationRead" lastSync: description: epoch time of last sync. null if no sync has taken place. type: integer diff --git a/airbyte-integrations/connector-templates/generator/plopfile.js b/airbyte-integrations/connector-templates/generator/plopfile.js index ab9b619596dbf..11c062b2f7b53 100644 --- a/airbyte-integrations/connector-templates/generator/plopfile.js +++ b/airbyte-integrations/connector-templates/generator/plopfile.js @@ -64,11 +64,24 @@ module.exports = function (plop) { templateFiles: `${pythonSourceInputRoot}/**/**`, globOptions: {ignore:'.secrets'} }, + // plop doesn't add dotfiles by default so we manually add them { type:'add', abortOnFail: true, - templateFile: `${pythonSourceInputRoot}/.secrets/credentials.json.hbs`, - path: `${pythonSourceOutputRoot}/secrets/credentials.json` + templateFile: `${pythonSourceInputRoot}/.secrets/config.json.hbs`, + path: `${pythonSourceOutputRoot}/secrets/config.json` + }, + { + type:'add', + abortOnFail: true, + templateFile: `${pythonSourceInputRoot}/.gitignore.hbs`, + path: `${pythonSourceOutputRoot}/.gitignore` + }, + { + type:'add', + abortOnFail: true, + templateFile: `${pythonSourceInputRoot}/.dockerignore.hbs`, + path: `${pythonSourceOutputRoot}/.dockerignore` }, function(answers, config, plop){ const renderedOutputDir = plop.renderString(pythonSourceOutputRoot, answers); @@ -93,8 +106,20 @@ module.exports = function (plop) { { type:'add', abortOnFail: true, - templateFile: `${singerSourceInputRoot}/.secrets/credentials.json.hbs`, - path: `${singerSourceOutputRoot}/secrets/credentials.json` + templateFile: `${singerSourceInputRoot}/.secrets/config.json.hbs`, + path: `${singerSourceOutputRoot}/secrets/config.json` + }, + { + type:'add', + abortOnFail: true, + templateFile: `${singerSourceInputRoot}/.gitignore.hbs`, + path: `${singerSourceOutputRoot}/.gitignore` + }, + { + type:'add', + abortOnFail: true, + templateFile: `${singerSourceInputRoot}/.dockerignore.hbs`, + path: `${singerSourceOutputRoot}/.dockerignore` }, function(answers, config, plop){ const renderedOutputDir = plop.renderString(singerSourceOutputRoot, answers); diff --git a/airbyte-integrations/connector-templates/source-python/.secrets/credentials.json.hbs b/airbyte-integrations/connector-templates/source-python/.secrets/credentials.json.hbs deleted file mode 100644 index 69c2fd063eb7b..0000000000000 --- a/airbyte-integrations/connector-templates/source-python/.secrets/credentials.json.hbs +++ /dev/null @@ -1,4 +0,0 @@ -{ - // TODO populate with needed credentials for integration tests or delete this file and any references to it - // the schema of this file should match what is in your spec.json -} diff --git a/airbyte-integrations/connector-templates/source-python/Dockerfile.test.hbs b/airbyte-integrations/connector-templates/source-python/Dockerfile.test.hbs index bcd00627cc97a..890129b8a35a4 100644 --- a/airbyte-integrations/connector-templates/source-python/Dockerfile.test.hbs +++ b/airbyte-integrations/connector-templates/source-python/Dockerfile.test.hbs @@ -12,8 +12,8 @@ LABEL io.airbyte.name=airbyte/source-{{dashCase name}}-standard-test WORKDIR /airbyte/integration_code COPY source_{{snakeCase name}} source_{{snakeCase name}} COPY $CODE_PATH $CODE_PATH -COPY secrets/* $CODE_PATH -COPY source_{{snakeCase name}}/*.json $CODE_PATH +COPY secrets/* $CODE_PATH/ +COPY source_{{snakeCase name}}/*.json $CODE_PATH/ COPY setup.py ./ RUN pip install ".[tests]" diff --git a/airbyte-integrations/connector-templates/source-python/NEW_SOURCE_CHECKLIST.md.hbs b/airbyte-integrations/connector-templates/source-python/NEW_SOURCE_CHECKLIST.md.hbs index eba3e502a55fe..56bab6d2b19ac 100644 --- a/airbyte-integrations/connector-templates/source-python/NEW_SOURCE_CHECKLIST.md.hbs +++ b/airbyte-integrations/connector-templates/source-python/NEW_SOURCE_CHECKLIST.md.hbs @@ -4,7 +4,7 @@ This is an autogenerated file describing the high-level steps you need to take t 1. First, build the module by running the following from the `airbyte` project root directory: ``` - ./gradlew :airbyte-integrations:connectors:source-{dashCase name}:build + ./gradlew :airbyte-integrations:connectors:source-{{dashCase name}}:build ``` 1. Define the specification for this source connector by modifying `source_{{snakeCase name}}/spec.json`. A specification is a JSON file which uses JSONSchema to declare all the inputs needed for your integration to function @@ -21,9 +21,19 @@ This is an autogenerated file describing the high-level steps you need to take t 1. Create tests for your integration. 1. Unit tests go in the `unit_tests` folder. 1. Integration tests go in the `integration_tests` folder. Any tests that require external APIs or resources, you may need to give instructions on how to set up a testing instance/account for Airbyte's CI. - However, for initial local development, you can place any sensitive credentials inside the `secrets/credentials.json` file -- this is gitignored by default. + However, for initial local development, you can place any sensitive credentials inside the `secrets/config.json` file -- this is gitignored by default. 1. Update `README.md` to document the usage of your integration. If API credentials are required to run the integration, please document how they can be obtained or link to a how-to guide. 1. For Airbyte core contributors, make sure to add the secret to RPass under the secret name as listed in `README.md`. -1. Add your source to the source definition registry in `airbyte-config/init`. +1. So far, all we've done is create the integration. Now let's make it available for use from the Airbyte UI and API! Add your source to the source + definition registry in the `airbyte-config/init/src/main/resources/STANDARD_SOURCE_DEFINITION/` directory. This directory "registers" all the source definitions which ship + with Airbyte out of the box. First, generate a UUIDv4, then create a JSON file named `.json` in the directory above. Copy the + contents of one of the other files in that directory as a starting point and edit the fields as appropriate to your integration. +1. To be able to run the integration tests for this connector in CI, add your config.json to the Airbyte repo Github secrets. If you are unable to provide + your own credentials (e.g: they're for your personal or employer's account), create an issue in the Airbyte Github repo with directions on how the + Airbyte team can create credentials and run them against your integration. +1. Once the config is stored in Github Secrets, edit `.github/workflows/gradle.yaml` to inject the config into the build environment. +1. Edit the `airbyte/tools/bin/ci_credentials.sh` script to pull the script from the build environment and write it to `secrets/config.json` during the build. +1. From the `airbyte` project root, run `./gradlew build` to make sure your module builds within the rest of the monorepo. Commit any changes, such as styling changes. +1. Create a PR against the Airbyte repo with your changes. Once you've done all the above, delete this file :) diff --git a/airbyte-integrations/connector-templates/source-python/README.md.hbs b/airbyte-integrations/connector-templates/source-python/README.md.hbs index 6aaeb85071ef9..814578be52ec0 100644 --- a/airbyte-integrations/connector-templates/source-python/README.md.hbs +++ b/airbyte-integrations/connector-templates/source-python/README.md.hbs @@ -4,28 +4,39 @@ This is the repository for the {{titleCase name}} source connector, written in P For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/{{dashCase name}}). ## Local development -### Build + +### Prerequisites +**To iterate on this connector, make sure to complete this prerequisites section.** + +#### Build & Activate Virtual Environment First, build the module by running the following from the `airbyte` project root directory: ``` ./gradlew :airbyte-integrations:connectors:source-{{dashCase name}}:build ``` -This should generate a virtualenv for this module in `source-{{dashCase name}}/.venv`. Make sure this venv is active in your -development environment of choice. If you are on the terminal, run the following from the `source-{{dashCase name}}` directory: +This will generate a virtualenv for this module in `source-{{dashCase name}}/.venv`. Make sure this venv is active in your +development environment of choice. To activate the venv from the terminal, run: ``` cd airbyte-integrations/connectors/source-{{dashCase name}} # cd into the connector directory source .venv/bin/activate ``` -If you are in an IDE, follow your IDE's instructions to activate the virtualenv. +If you are in an IDE, follow your IDE's instructions to activate the virtualenv. + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/{{dashCase name}}) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_{{snakeCase name}}/spec.json` file. +See `sample_files/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in RPass under the secret name `source-{{dashCase name}}-integration-test-config` +and place them into `secrets/config.json`. -**All the instructions below assume you have correctly activated the virtualenv.**. ### Locally running the connector ``` python main_dev.py spec -python main_dev.py check --config sample_files/test_config.json -python main_dev.py discover --config sample_files/test_config.json -python main_dev.py read --config sample_files/test_config.json --catalog sample_files/test_catalog.json +python main_dev.py check --config secrets/config.json +python main_dev.py discover --config secrets/config.json +python main_dev.py read --config secrets/config.json --catalog sample_files/sample_catalog.json ``` ### Unit Tests @@ -38,25 +49,16 @@ pytest unit_tests ``` # in airbyte root directory ./gradlew :airbyte-integrations:connectors:source-{{dashCase name}}:buildImage -docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}/sample_files:/sample_files airbyte/source-{{dashCase name}}:dev spec -docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}/sample_files:/sample_files airbyte/source-{{dashCase name}}:dev check --config /sample_files/test_config.json -docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}/sample_files:/sample_files airbyte/source-{{dashCase name}}:dev discover --config /sample_files/test_config.json -docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}/sample_files:/sample_files airbyte/source-{{dashCase name}}:dev read --config /sample_files/test_config.json --catalog /sample_files/test_catalog.json +docker run --rm airbyte/source-{{dashCase name}}:dev spec +docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}/secrets:/secrets airbyte/source-{{dashCase name}}:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}/secrets:/secrets airbyte/source-{{dashCase name}}:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}/secrets:/secrets -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}/sample_files:/sample_files airbyte/source-{{dashCase name}}:dev read --config /secrets/config.json --catalog /sample_files/sample_catalog.json ``` -### Integration Tests -1. Configure credentials as appropriate, described below. +### Integration Tests 1. From the airbyte project root, run `./gradlew :airbyte-integrations:connectors:source-{{dashCase name}}:standardSourceTestPython` to run the standard integration test suite. 1. To run additional integration tests, place your integration tests in the `integration_tests` directory and run them with `pytest integration_tests`. Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. ## Dependency Management All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. - -## Configure credentials -### Configuring credentials as a community contributor -Follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/{{dashCase name}}) to generate credentials, then put those -in `secrets/credentials.json`. - -### Airbyte Employee -Credentials are available in RPass under the secret name `source-{{dashCase name}}-integration-test-creds`. diff --git a/airbyte-integrations/connector-templates/source-python/setup.py.hbs b/airbyte-integrations/connector-templates/source-python/setup.py.hbs index 5b901d0197bfe..ac50b2d8f5315 100644 --- a/airbyte-integrations/connector-templates/source-python/setup.py.hbs +++ b/airbyte-integrations/connector-templates/source-python/setup.py.hbs @@ -41,7 +41,7 @@ setup( # Dependencies required by the main package but not integration tests should go in main. Deps required by # integration tests but not the main package go in integration_tests. Deps required by both should go in # install_requires. - "main":[], + "main": [], "tests": ["airbyte_python_test", "pytest"], }, ) diff --git a/airbyte-integrations/connector-templates/source-singer/.secrets/credentials.json.hbs b/airbyte-integrations/connector-templates/source-singer/.secrets/credentials.json.hbs deleted file mode 100644 index 77dd96a3b020e..0000000000000 --- a/airbyte-integrations/connector-templates/source-singer/.secrets/credentials.json.hbs +++ /dev/null @@ -1,4 +0,0 @@ -{ - // TODO populate with needed credentials for integration tests or delete this file and any references to it - // the schema of this file should match what is in your spec.json -} diff --git a/airbyte-integrations/connector-templates/source-singer/Dockerfile.test.hbs b/airbyte-integrations/connector-templates/source-singer/Dockerfile.test.hbs index e61ca83ff9e0e..8365867dd9084 100644 --- a/airbyte-integrations/connector-templates/source-singer/Dockerfile.test.hbs +++ b/airbyte-integrations/connector-templates/source-singer/Dockerfile.test.hbs @@ -14,8 +14,8 @@ LABEL io.airbyte.name=airbyte/source-{{dashCase name}}-singer-standard-test WORKDIR /airbyte/integration_code COPY $MODULE_NAME $MODULE_NAME COPY $CODE_PATH $CODE_PATH -COPY secrets/* $CODE_PATH -COPY $MODULE_NAME/*.json $CODE_PATH +COPY secrets/* $CODE_PATH/ +COPY $MODULE_NAME/*.json $CODE_PATH/ COPY setup.py ./ RUN pip install ".[tests]" diff --git a/airbyte-integrations/connector-templates/source-singer/NEW_SOURCE_CHECKLIST.md.hbs b/airbyte-integrations/connector-templates/source-singer/NEW_SOURCE_CHECKLIST.md.hbs index 5718b26439b76..9389e4caaee87 100644 --- a/airbyte-integrations/connector-templates/source-singer/NEW_SOURCE_CHECKLIST.md.hbs +++ b/airbyte-integrations/connector-templates/source-singer/NEW_SOURCE_CHECKLIST.md.hbs @@ -2,11 +2,11 @@ This is an autogenerated file describing the steps needed to implement a new Airbyte source based on a Singer Tap. +1. Include the Singer Tap you'd like to build this Source on top by including it in `setup.py` as a dependency. 1. First, build the module by running the following from the `airbyte` project root directory: ``` - ./gradlew :airbyte-integrations:connectors:source-{dashCase name}-singer:build + ./gradlew :airbyte-integrations:connectors:source-{{dashCase name}}-singer:build ``` -1. Include the Singer Tap you'd like to build this Source on top by including it in `setup.py` as a dependency. 1. Define the specification for this source connector by modifying `source_{{snakeCase name}}_singer/spec.json`. A specification is a JSON file which uses JSONSchema to declare all the inputs needed for your integration to function correctly. For example, if you were configuring a Postgres source, your specification might declare that you need a @@ -22,9 +22,19 @@ This is an autogenerated file describing the steps needed to implement a new Air 1. Create tests for your integration. 1. Unit tests go in the `unit_tests` folder. 1. Integration tests go in the `integration_tests` folder. For tests that require external APIs or resources, you may need to give instructions on how to set up a testing instance/account for Airbyte's CI. - However, for initial local development, you can place any sensitive credentials inside the `secrets/credentials.json` file to use within your tests. The `secrets` directory is gitignored by default. + However, for initial local development, you can place any sensitive credentials inside the `secrets/config.json` file to use within your tests. The `secrets` directory is gitignored by default. 1. Update `README.md` to document the usage of your integration. If API credentials are required to run the integration, please document how they can be obtained or link to a how-to guide. 1. For Airybte core contributors, make sure to add the secret to RPass under the secret name as listed in `README.md`. -1. Add your source to the source definition registry in `airbyte-config/init`. +1. So far, all we've done is create the integration. Now let's make it available for use from the Airbyte UI and API! Add your source to the source + definition registry in the `airbyte-config/init/src/main/resources/STANDARD_SOURCE_DEFINITION/` directory. This directory "registers" all the source definitions which ship + with Airbyte out of the box. First, generate a UUIDv4, then create a JSON file named `.json` in the directory above. Copy the + contents of one of the other files in that directory as a starting point and edit the fields as appropriate to your integration. +1. To be able to run the integration tests for this connector in CI, add your config.json to the Airbyte repo Github secrets. If you are unable to provide + your own credentials (e.g: they're for your personal or employer's account), create an issue in the Airbyte Github repo with directions on how the + Airbyte team can create credentials and run them against your integration. +1. Once the config is stored in Github Secrets, edit `.github/workflows/gradle.yaml` to inject the config into the build environment. +1. Edit the `airbyte/tools/bin/ci_credentials.sh` script to pull the script from the build environment and write it to `secrets/config.json` during the build.\ +1. From the `airbyte` project root, run `./gradlew build` to make sure your module builds within the rest of the monorepo. Commit any changes, such as styling changes. + 1. Create a PR against the Airbyte repo with your changes. Once you've done all the above, delete this file :) diff --git a/airbyte-integrations/connector-templates/source-singer/README.md.hbs b/airbyte-integrations/connector-templates/source-singer/README.md.hbs index 7127305f00729..426a386abfdad 100644 --- a/airbyte-integrations/connector-templates/source-singer/README.md.hbs +++ b/airbyte-integrations/connector-templates/source-singer/README.md.hbs @@ -4,28 +4,37 @@ This is the repository for the {{titleCase name}} source connector, based on a S For information about how to use this connector within Airbyte, see [the User Documentation](https://docs.airbyte.io/integrations/sources/{{dashCase name}}). ## Local development -### Build +### Prerequisites +**To iterate on this connector, make sure to complete this prerequisites section.** + +#### Build & Activate Virtual Environment First, build the module by running the following from the `airbyte` project root directory: ``` ./gradlew :airbyte-integrations:connectors:source-{{dashCase name}}-singer:build ``` -This should generate a virtualenv for this module in `source-{{dashCase name}}-singer/.venv`. Make sure this venv is active in your -development environment of choice. If you are on the terminal, run the following +This will generate a virtualenv for this module in `source-{{dashCase name}}-singer/.venv`. Make sure this venv is active in your +development environment of choice. To activate the venv from the terminal, run: ``` cd airbyte-integrations/connectors/source-{{dashCase name}} # cd into the connector directory source .venv/bin/activate ``` -If you are in an IDE, follow your IDE's instructions to activate the virtualenv. +If you are in an IDE, follow your IDE's instructions to activate the virtualenv. + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/{{dashCase name}}) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_{{snakeCase name}}_singer/spec.json` file. +See `sample_files/sample_config.json` for a sample config file. -**All the instructions below assume you have correctly activated the virtualenv.**. +**If you are an Airbyte core member**, copy the credentials in RPass under the secret name `source-{{dashCase name}}-singer-integration-test-config` +and place them into `secrets/config.json`. ### Locally running the connector ``` python main_dev.py spec -python main_dev.py check --config sample_files/test_config.json -python main_dev.py discover --config sample_files/test_config.json -python main_dev.py read --config sample_files/test_config.json --catalog sample_files/test_catalog.json +python main_dev.py check --config secrets/config.json +python main_dev.py discover --config secrets/config.json +python main_dev.py read --config secrets/config.json --catalog sample_files/sample_catalog.json ``` ### Unit Tests @@ -34,30 +43,20 @@ To run unit tests locally, from the connector root run: pytest unit_tests ``` - ### Locally running the connector docker image ``` # in airbyte root directory ./gradlew :airbyte-integrations:connectors:source-{{dashCase name}}-singer:buildImage -docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}-singer/sample_files:/sample_files airbyte/source-{{dashCase name}}-singer:dev spec -docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}-singer/sample_files:/sample_files airbyte/source-{{dashCase name}}-singer:dev check --config /sample_files/test_config.json -docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}-singer/sample_files:/sample_files airbyte/source-{{dashCase name}}-singer:dev discover --config /sample_files/test_config.json -docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}-singer/sample_files:/sample_files airbyte/source-{{dashCase name}}-singer:dev read --config /sample_files/test_config.json --catalog /sample_files/test_catalog.json +docker run --rm airbyte/source-{{dashCase name}}-singer:dev spec +docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}-singer/secrets:/secrets airbyte/source-{{dashCase name}}-singer:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}-singer/secrets:/secrets airbyte/source-{{dashCase name}}-singer:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}-singer/secrets:/secrets -v $(pwd)/airbyte-integrations/connectors/source-{{dashCase name}}-singer/sample_files:/sample_files airbyte/source-{{dashCase name}}-singer:dev read --config /secrets/config.json --catalog /sample_files/sample_catalog.json ``` -### Integration Tests -1. Configure credentials as appropriate, described below. +### Integration Tests 1. From the airbyte project root, run `./gradlew :airbyte-integrations:connectors:source-{{dashCase name}}-singer:standardSourceTestPython` to run the standard integration test suite. 1. To run additional integration tests, place your integration tests in the `integration_tests` directory and run them with `pytest integration_tests`. Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. ## Dependency Management All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. - -## Configure credentials -### Configuring credentials as a community contributor -Follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/{{dashCase name}}) to generate credentials, then put those -in `secrets/credentials.json`. - -### Airbyte Employee -Credentials are available in RPass under the secret name `source-{{dashCase name}}-singer-integration-test-creds`. diff --git a/airbyte-integrations/connector-templates/source-singer/setup.py.hbs b/airbyte-integrations/connector-templates/source-singer/setup.py.hbs index b509baf6f297b..eded88b90d4d3 100644 --- a/airbyte-integrations/connector-templates/source-singer/setup.py.hbs +++ b/airbyte-integrations/connector-templates/source-singer/setup.py.hbs @@ -38,7 +38,7 @@ setup( # Dependencies required by the main package but not integration tests should go in main. Deps required by # integration tests but not the main package go in integration_tests. Deps required by both should go in # install_requires. - "main":["base-singer", "base-python"], + "main": ["base-singer", "base-python"], "tests": ["airbyte_python_test", "pytest"], }, ) diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java index 804465cd92236..ea0fb9b73d366 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java @@ -117,7 +117,7 @@ public ConfigurationApi(final ConfigRepository configRepository, final Scheduler new DestinationHandler(configRepository, schemaValidator, schedulerHandler, connectionsHandler); sourceHandler = new SourceHandler(configRepository, schemaValidator, schedulerHandler, connectionsHandler); jobHistoryHandler = new JobHistoryHandler(schedulerPersistence); - webBackendConnectionsHandler = new WebBackendConnectionsHandler(connectionsHandler, sourceHandler, jobHistoryHandler); + webBackendConnectionsHandler = new WebBackendConnectionsHandler(connectionsHandler, sourceHandler, destinationHandler, jobHistoryHandler); webBackendSourceHandler = new WebBackendSourceHandler(sourceHandler, schedulerHandler); webBackendDestinationHandler = new WebBackendDestinationHandler(destinationHandler, schedulerHandler); debugInfoHandler = new DebugInfoHandler(configRepository); diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java index 177db77d953fe..8978c96009862 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java @@ -27,6 +27,8 @@ import com.google.common.collect.Lists; import io.airbyte.api.model.ConnectionIdRequestBody; import io.airbyte.api.model.ConnectionRead; +import io.airbyte.api.model.DestinationIdRequestBody; +import io.airbyte.api.model.DestinationRead; import io.airbyte.api.model.JobConfigType; import io.airbyte.api.model.JobListRequestBody; import io.airbyte.api.model.JobRead.StatusEnum; @@ -46,13 +48,16 @@ public class WebBackendConnectionsHandler { private final ConnectionsHandler connectionsHandler; private final SourceHandler sourceHandler; + private final DestinationHandler destinationHandler; private final JobHistoryHandler jobHistoryHandler; public WebBackendConnectionsHandler(final ConnectionsHandler connectionsHandler, final SourceHandler sourceHandler, + final DestinationHandler destinationHandler, final JobHistoryHandler jobHistoryHandler) { this.connectionsHandler = connectionsHandler; this.sourceHandler = sourceHandler; + this.destinationHandler = destinationHandler; this.jobHistoryHandler = jobHistoryHandler; } @@ -76,6 +81,9 @@ private WbConnectionRead buildWbConnectionRead(ConnectionRead connectionRead) th .sourceId(connectionRead.getSourceId()); final SourceRead source = sourceHandler.getSource(sourceIdRequestBody); + final DestinationIdRequestBody destinationIdRequestBody = new DestinationIdRequestBody().destinationId(connectionRead.getDestinationId()); + final DestinationRead destination = destinationHandler.getDestination(destinationIdRequestBody); + final JobListRequestBody jobListRequestBody = new JobListRequestBody() .configId(connectionRead.getConnectionId().toString()) .configType(JobConfigType.SYNC); @@ -89,7 +97,8 @@ private WbConnectionRead buildWbConnectionRead(ConnectionRead connectionRead) th .status(connectionRead.getStatus()) .syncMode(Enums.convertTo(connectionRead.getSyncMode(), WbConnectionRead.SyncModeEnum.class)) .schedule(connectionRead.getSchedule()) - .source(source); + .source(source) + .destination(destination); final JobReadList jobReadList = jobHistoryHandler.listJobsFor(jobListRequestBody); wbConnectionRead.setIsSyncing( diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java index 45d9ee4fa46d6..018b709b2570d 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java @@ -31,6 +31,8 @@ import io.airbyte.api.model.ConnectionIdRequestBody; import io.airbyte.api.model.ConnectionRead; import io.airbyte.api.model.ConnectionReadList; +import io.airbyte.api.model.DestinationIdRequestBody; +import io.airbyte.api.model.DestinationRead; import io.airbyte.api.model.JobConfigType; import io.airbyte.api.model.JobListRequestBody; import io.airbyte.api.model.JobRead; @@ -41,11 +43,15 @@ import io.airbyte.api.model.WbConnectionReadList; import io.airbyte.api.model.WorkspaceIdRequestBody; import io.airbyte.commons.enums.Enums; +import io.airbyte.config.DestinationConnection; import io.airbyte.config.SourceConnection; +import io.airbyte.config.StandardDestinationDefinition; import io.airbyte.config.StandardSourceDefinition; import io.airbyte.config.StandardSync; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.server.helpers.ConnectionHelpers; +import io.airbyte.server.helpers.DestinationDefinitionHelpers; +import io.airbyte.server.helpers.DestinationHelpers; import io.airbyte.server.helpers.SourceDefinitionHelpers; import io.airbyte.server.helpers.SourceHelpers; import io.airbyte.validation.json.JsonValidationException; @@ -62,6 +68,7 @@ class WebBackendConnectionsHandlerTest { private WebBackendConnectionsHandler wbHandler; private SourceRead sourceRead; + private DestinationRead destinationRead; private ConnectionRead connectionRead; private WbConnectionRead expected; @@ -69,13 +76,18 @@ class WebBackendConnectionsHandlerTest { public void setup() throws IOException, JsonValidationException, ConfigNotFoundException { connectionsHandler = mock(ConnectionsHandler.class); SourceHandler sourceHandler = mock(SourceHandler.class); + DestinationHandler destinationHandler = mock(DestinationHandler.class); JobHistoryHandler jobHistoryHandler = mock(JobHistoryHandler.class); - wbHandler = new WebBackendConnectionsHandler(connectionsHandler, sourceHandler, jobHistoryHandler); + wbHandler = new WebBackendConnectionsHandler(connectionsHandler, sourceHandler, destinationHandler, jobHistoryHandler); final StandardSourceDefinition standardSourceDefinition = SourceDefinitionHelpers.generateSource(); SourceConnection source = SourceHelpers.generateSource(UUID.randomUUID()); sourceRead = SourceHelpers.getSourceRead(source, standardSourceDefinition); + final StandardDestinationDefinition destinationDefinition = DestinationDefinitionHelpers.generateDestination(); + final DestinationConnection destination = DestinationHelpers.generateDestination(UUID.randomUUID()); + destinationRead = DestinationHelpers.getDestinationRead(destination, destinationDefinition); + final StandardSync standardSync = ConnectionHelpers.generateSyncWithSourceId(source.getSourceId()); connectionRead = ConnectionHelpers.generateExpectedConnectionRead(standardSync); @@ -83,6 +95,10 @@ public void setup() throws IOException, JsonValidationException, ConfigNotFoundE sourceIdRequestBody.setSourceId(connectionRead.getSourceId()); when(sourceHandler.getSource(sourceIdRequestBody)).thenReturn(sourceRead); + final DestinationIdRequestBody destinationIdRequestBody = new DestinationIdRequestBody(); + destinationIdRequestBody.setDestinationId(connectionRead.getDestinationId()); + when(destinationHandler.getDestination(destinationIdRequestBody)).thenReturn(destinationRead); + Instant now = Instant.now(); final JobRead jobRead = new JobRead(); jobRead.setConfigId(connectionRead.getConnectionId().toString()); @@ -109,7 +125,8 @@ public void setup() throws IOException, JsonValidationException, ConfigNotFoundE expected.setStatus(connectionRead.getStatus()); expected.setSyncMode(Enums.convertTo(connectionRead.getSyncMode(), WbConnectionRead.SyncModeEnum.class)); expected.setSchedule(connectionRead.getSchedule()); - expected.setSource(this.sourceRead); + expected.setSource(sourceRead); + expected.setDestination(destinationRead); expected.setLastSync(now.getEpochSecond()); expected.isSyncing(false); } diff --git a/build.gradle b/build.gradle index 55a865911806e..632d0c3b1184c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,8 @@ plugins { id 'base' - id 'java' id 'pmd' - id 'com.diffplug.spotless' version '5.6.1' + id 'com.diffplug.spotless' version '5.7.0' id 'ru.vyarus.use-python' version '2.2.0' apply false - // id 'de.aaschmid.cpd' version '3.1' } repositories { @@ -18,39 +16,46 @@ if (!env.containsKey('VERSION')) { throw new Exception('Version not specified in .env file...') } -def createJavaLicenseWith = { licence -> +def createLicenseWith = { File license, String startComment, String endComment, String lineComment -> def tmp = File.createTempFile('tmp', '.tmp') tmp.withWriter { def w = it - w.writeLine("/*") - licence.eachLine { - w << " * " + w.writeLine(startComment) + license.eachLine { + w << lineComment w.writeLine(it) } - w.writeLine(" */") + w.writeLine(endComment) w.writeLine("") } return tmp } -def createPythonLicenseWith = { licence -> - def tmp = File.createTempFile('tmp', '.tmp') - tmp.withWriter { - def w = it - w.writeLine('"""') - licence.eachLine { - w.writeLine(it) - } - w.writeLine('"""') - w.writeLine("") - } - return tmp +def createPythonLicenseWith = { license -> + return createLicenseWith(license, '"""', '"""', "") +} + +def createJavaLicenseWith = { license -> + return createLicenseWith(license, '/*', ' */', " * ") +} + +// We are the spotless exclusions rules using file tree. It seems the excludeTarget option is super finicky in a +// monorepo setup and it doesn't actually exclude directories reliably. This code makes the behavior predictable. +def createSpotlessTarget = { pattern -> + def excludes = [ + '.gradle', + 'node_modules', + '.eggs', + '.mypy_cache', + '.venv', + '*.egg-info', + ] + return fileTree(dir: rootDir, include: pattern, exclude: excludes.collect {"**/${it}"}) } spotless { java { - target '**/*.java' - targetExclude "**/build/**/*", "**/.gradle/**/*" + target createSpotlessTarget('**/*.java') importOrder() @@ -61,39 +66,26 @@ spotless { trimTrailingWhitespace() } groovyGradle { - target '**/*.gradle' - targetExclude "**/build/**/*", "**/.gradle/**/*" + target createSpotlessTarget('**/*.gradle') } sql { - target '**/*.sql' - targetExclude "**/build/**/*", "**/.gradle/**/*", "**/.venv/**", "airbyte-integrations/bases/base-normalization/dbt-project-template/**/*" + target createSpotlessTarget('**/*.sql') dbeaver().configFile(rootProject.file('tools/gradle/codestyle/sql-dbeaver.properties')) } python { - target '**/*.py' - targetExclude "**/build/**/*", "**/.gradle/**/*", "**/.venv/**/*", "**/.eggs/**/*", "**/.mypy_cache/**/*" + target createSpotlessTarget('**/*.py') + licenseHeaderFile createPythonLicenseWith(rootProject.file('LICENSE')), '(from|import|# generated)' } format 'styling', { - target '**/*.json', '**/*.yaml' - targetExclude "**/build/**/*", "**/node_modules/**/*", "**/.gradle/**/*" + target createSpotlessTarget(['**/*.yaml', '**/*.json']) prettier() } } check.dependsOn 'spotlessApply' -// Disabled because it generate an obnoxious warning -// TODO: https://github.com/airbytehq/airbyte/issues/225 -// -// cpdCheck { -// ignoreFailures = true -// reports { -// text.enabled = true -// } -// } - allprojects { apply plugin: 'base' diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 831ca7b81db9a..39975cbb76a3d 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -20,6 +20,7 @@ * [Google Analytics](integrations/sources/googleanalytics.md) * [Google Sheets](integrations/sources/google-sheets.md) * [Hubspot](integrations/sources/hubspot.md) + * [Marketo](integrations/sources/marketo.md) * [MySQL](integrations/sources/mysql.md) * [Postgres](integrations/sources/postgres.md) * [Rest API](integrations/sources/rest-api.md) diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 464c244711f93..a250d4976895e 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -4,7 +4,7 @@ description: Here is a high level view of Airbyte's components. # Architecture -![3.048-Kilometer view](../.gitbook/assets/10-000-feet-view%20%281%29%20%281%29%20%281%29%20%282%29.png) +![3.048-Kilometer view](../.gitbook/assets/10-000-feet-view%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29.png) * `UI`: Acts as the control center for Airbyte. From the UI, you can configure new integration connections. You can also track the different syncing jobs and view logs. * `Config Store`: Stores all the connections information \(credentials, frequency...\). diff --git a/docs/changelog.md b/docs/changelog.md index 9b61404ee3a4c..fd7864053fc4b 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -14,15 +14,15 @@ Here is what we have in mind: * Support multiple destinations * **New destination:** our own Redshift warehouse integration -* **New sources:** 10 additional sources connectors. +* **New sources:** 10 additional sources connectors, including MSSQL, GCS CSV, S3 CSV, SFTP CSV +* As a bonus if we can, update the onboarding experience with pre-filled demo data for the users who just want to see how Airbyte works with the least effort. -## 0.4.0 - expected around 11/04/2020 +## 0.4.0 - delivered on 11/04/2020 Here is what we are working on right now: * **New destination**: our own **Snowflake** warehouse integration -* **New sources:** Facebook Ads, Google Ads, MSSQL, GCS CSV, S3 CSV, SFTP CSV -* as a bonus if we can, update the onboarding experience with pre-filled demo data for the users who just want to see how Airbyte works with the least effort. +* **New sources:** Facebook Ads, Google Ads. ## 0.3.0 - delivered on 10/30/2020 diff --git a/docs/deploying-airbyte/on-aws-ec2.md b/docs/deploying-airbyte/on-aws-ec2.md index 0b5e3c396e5eb..c90c4de43abd9 100644 --- a/docs/deploying-airbyte/on-aws-ec2.md +++ b/docs/deploying-airbyte/on-aws-ec2.md @@ -8,15 +8,15 @@ The instructions have been tested on `Amazon Linux 2 AMI (HVM)` * Launch a new instance -![](../.gitbook/assets/aws_ec2_launch%20%281%29%20%281%29%20%281%29%20%282%29.png) +![](../.gitbook/assets/aws_ec2_launch%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29.png) * Select instance AMI -![](../.gitbook/assets/aws_ec2_ami%20%281%29%20%281%29%20%281%29%20%282%29.png) +![](../.gitbook/assets/aws_ec2_ami%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29.png) * Select instance type -![](../.gitbook/assets/aws_ec2_instance_type%20%281%29%20%281%29%20%281%29%20%282%29.png) +![](../.gitbook/assets/aws_ec2_instance_type%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29.png) * `Next: Configure Instance Details` * You can tune parameters or keep the defaults @@ -27,7 +27,7 @@ The instructions have been tested on `Amazon Linux 2 AMI (HVM)` * `Next: Configure Security Groups` * We are going to allow network for `ssh` -![](../.gitbook/assets/aws_ec2_security_group%20%281%29%20%281%29%20%281%29%20%282%29.png) +![](../.gitbook/assets/aws_ec2_security_group%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29.png) * `Review and Launch` * `Launch` @@ -38,7 +38,7 @@ The instructions have been tested on `Amazon Linux 2 AMI (HVM)` * `Launch Instances` -![](../.gitbook/assets/aws_ec2_instance_view%20%281%29%20%281%29%20%281%29%20%282%29.png) +![](../.gitbook/assets/aws_ec2_instance_view%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29.png) * Wait for the instance to become `Running` diff --git a/docs/deploying-airbyte/on-gcp-compute-engine.md b/docs/deploying-airbyte/on-gcp-compute-engine.md index 8eca44ed8c855..138e082cde145 100644 --- a/docs/deploying-airbyte/on-gcp-compute-engine.md +++ b/docs/deploying-airbyte/on-gcp-compute-engine.md @@ -8,11 +8,11 @@ The instructions have been tested on `Debian GNU/Linux 10 (buster)` * Launch a new instance -![](../.gitbook/assets/gcp_ce_launch%20%281%29%20%281%29%20%281%29%20%282%29.png) +![](../.gitbook/assets/gcp_ce_launch%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29.png) * Configure new instance -![](../.gitbook/assets/gcp_ce_configure%20%281%29%20%281%29%20%281%29%20%282%29.png) +![](../.gitbook/assets/gcp_ce_configure%20%281%29%20%281%29%20%281%29%20%282%29%20%281%29.png) * `Create` diff --git a/docs/getting-started-tutorial.md b/docs/getting-started-tutorial.md index 45d39ce97d070..af086da0eee5e 100644 --- a/docs/getting-started-tutorial.md +++ b/docs/getting-started-tutorial.md @@ -24,13 +24,13 @@ Once you see an Airbyte banner, the UI is ready to go at [http://localhost:8000/ You should see an onboarding page. Enter your email if you want updates about Airbyte and continue. -![](.gitbook/assets/airbyte_get-started.png) +![](.gitbook/assets/airbyte_get-started%20%281%29.png) ## 2. Set up your first connection Now you will see a wizard that allows you choose the data you want to send through Airbyte. -![](.gitbook/assets/02_set-up-sources%20%281%29.png) +![](.gitbook/assets/02_set-up-sources%20%281%29%20%281%29.png) As of our alpha launch, we have one database source \(Postgres\) and two API sources \(an exchange rate API and the Stripe API\). We're currently building an integration framework that makes it easy to create sources and destinations, so you should expect many more soon. Please reach out to us if you need a specific integration or would like to help build one. @@ -76,7 +76,7 @@ DB Name: postgres After adding the destination, you can choose what tables and columns you want to sync. -![](.gitbook/assets/03_set-up-connection%20%281%29.png) +![](.gitbook/assets/03_set-up-connection%20%281%29%20%281%29.png) For this demo, we recommend leaving the defaults and selecting "Every 5 Minutes" as the frequency. Click `Set Up Connection` to finish setting up the sync. @@ -84,7 +84,7 @@ For this demo, we recommend leaving the defaults and selecting "Every 5 Minutes" You should now see a list of sources with the source you just added. Click on it to find more information about your connection. This is the page where you can update any settings about this source and how it syncs. There should be a `Completed` job under the history section. If you click on that run, it will show logs from that run. -![](.gitbook/assets/04_source-details%20%281%29.png) +![](.gitbook/assets/04_source-details%20%281%29%20%281%29.png) One of biggest problems we've seen in tools like Fivetran is the lack of visibility when debugging. In Airbyte, allowing full log access and the ability to debug and fix integration problems is one of our highest priorities. We'll be working hard to make these logs accessible and understandable. diff --git a/docs/integrations/destinations/snowflake.md b/docs/integrations/destinations/snowflake.md index 648b69c2008cb..edc1f82bd390a 100644 --- a/docs/integrations/destinations/snowflake.md +++ b/docs/integrations/destinations/snowflake.md @@ -16,23 +16,17 @@ Each stream will be output into its own table in Snowflake. Each table will cont #### Features - | Feature | Supported?\(Yes/No\) | Notes | | :--- | :--- | :--- | | Full Refresh Sync | Yes | | ## Getting started -We recommend creating an Airbyte-specific warehouse, database, schema, user, and role for writing data into Snowflake so -it is possible to track costs specifically related to Airbyte (including the cost of running this warehouse) and control permissions at a granular level. -Since the Airbyte user creates, drops, and alters tables, `OWNERSHIP` permissions are required in Snowflake. If you are not -following the recommended script below, please limit the `OWNERSHIP` permissions to only the necessary database and schema for the Airbyte user. +We recommend creating an Airbyte-specific warehouse, database, schema, user, and role for writing data into Snowflake so it is possible to track costs specifically related to Airbyte \(including the cost of running this warehouse\) and control permissions at a granular level. Since the Airbyte user creates, drops, and alters tables, `OWNERSHIP` permissions are required in Snowflake. If you are not following the recommended script below, please limit the `OWNERSHIP` permissions to only the necessary database and schema for the Airbyte user. -We provide the following script to create these resources. -Before running, you must change the password to something secure. -You may change the names of the other resources if you desire. +We provide the following script to create these resources. Before running, you must change the password to something secure. You may change the names of the other resources if you desire. -``` +```text -- set variables (these need to be uppercase) set airbyte_role = 'AIRBYTE_ROLE'; set airbyte_username = 'AIRBYTE_USER'; @@ -106,3 +100,4 @@ You should now have all the requirements needed to configure Snowflake as a dest * **Schema** * **Username** * **Password** + diff --git a/docs/integrations/integrations-changelog.md b/docs/integrations/integrations-changelog.md index f7a03055948a6..ab79d2c589a4c 100644 --- a/docs/integrations/integrations-changelog.md +++ b/docs/integrations/integrations-changelog.md @@ -10,8 +10,13 @@ Note: Airbyte is not built on top of Singer, but is compatible with Singer's pro ## Currently under construction -**New sources:** Hubspot, Facebook Ads, Google Ads, MSSQL, GCS CSV, S3 CSV, SFTP CSV -**New destinations:** Snowflake +**New sources:** MSSQL, GCS CSV, S3 CSV, SFTP CSV +**New destinations:** Redshift + +## 11/04/2020 + +**New sources:** [Facebook Ads](sources/facebook-marketing-api.md), [Google Ads](sources/google-adwords.md) +**New destination:** [Snowflake](destinations/snowflake.md) ## 10/30/2020 diff --git a/docs/integrations/sources/facebook-marketing-api.md b/docs/integrations/sources/facebook-marketing-api.md index ce5b3a7cb47b2..e4c98ca0a8220 100644 --- a/docs/integrations/sources/facebook-marketing-api.md +++ b/docs/integrations/sources/facebook-marketing-api.md @@ -2,20 +2,20 @@ ## Sync overview -This source can sync data for the core Ad Campaign data available[ in the Facebook Marketing API](https://developers.facebook.com/docs/marketing-api/campaign-structure): Campaigns, AdSets, Ads, and AdCreatives. It can also sync [Ad Insights from the Reporting API](https://developers.facebook.com/docs/marketing-api/insights). +This source can sync data for the core Ad Campaign data available[ in the Facebook Marketing API](https://developers.facebook.com/docs/marketing-api/campaign-structure): Campaigns, AdSets, Ads, and AdCreatives. It can also sync [Ad Insights from the Reporting API](https://developers.facebook.com/docs/marketing-api/insights). This Source Connector is based on the [Singer Facebook Tap](https://github.com/singer-io/tap-facebook). ### Output schema -This Source is capable of syncing the following core Streams: +This Source is capable of syncing the following core Streams: * AdSets. [Facebook docs](https://developers.facebook.com/docs/marketing-api/reference/ad-campaign#fields) * Ads. [Facebook docs](https://developers.facebook.com/docs/marketing-api/reference/adgroup#fields) * AdCreatives. [Facebook docs](https://developers.facebook.com/docs/marketing-api/reference/ad-creative#fields) * Campaigns. [Facebook docs](https://developers.facebook.com/docs/marketing-api/reference/ad-campaign-group#fields) -The linked Facebook docs go into detail about the fields present on those streams. +The linked Facebook docs go into detail about the fields present on those streams. In addition, this source is capable of syncing ad insights as a stream. Ad insights can also be segmented by the following categories, where each segment is synced as a separate Airbyte stream: @@ -25,7 +25,7 @@ In addition, this source is capable of syncing ad insights as a stream. Ad insig * Platform & Device * Region -The segmented streams contain entries of campaign/adset/ad combinations for each day broken down by the chosen segment. +The segmented streams contain entries of campaign/adset/ad combinations for each day broken down by the chosen segment. For more information, see the [Facebook Insights API documentation. ](https://developers.facebook.com/docs/marketing-api/reference/adgroup/insights/) @@ -47,11 +47,9 @@ For more information, see the [Facebook Insights API documentation. ](https://de ### Performance considerations -**Important note:** In order for data synced from your Facebook account to be up to date, you might need to apply with Facebook to upgrade your access token to the Ads Management Standard Tier as specified in the [Facebook Access documentation](https://developers.facebook.com/docs/marketing-api/access). Otherwise, Facebook might throttle Airbyte syncs, since the default tier \(Dev Access\) is heavily throttled by Facebook. +**Important note:** In order for data synced from your Facebook account to be up to date, you might need to apply with Facebook to upgrade your access token to the Ads Management Standard Tier as specified in the [Facebook Access documentation](https://developers.facebook.com/docs/marketing-api/access). Otherwise, Facebook might throttle Airbyte syncs, since the default tier \(Dev Access\) is heavily throttled by Facebook. - - -Note that Airbyte can adapt to throttling from Facebook. In the worst case scenario syncs from Facebook will take longer to complete and data will be less fresh. +Note that Airbyte can adapt to throttling from Facebook. In the worst case scenario syncs from Facebook will take longer to complete and data will be less fresh. ## Getting started @@ -65,27 +63,25 @@ Note that Airbyte can adapt to throttling from Facebook. In the worst case scena ### Facebook Ad Account ID -Follow the [Facebook documentation for obtaining your Ad Account ID](https://www.facebook.com/business/help/1492627900875762) and keep that on hand. We'll need this ID to configure Facebook as a source in Airbyte. +Follow the [Facebook documentation for obtaining your Ad Account ID](https://www.facebook.com/business/help/1492627900875762) and keep that on hand. We'll need this ID to configure Facebook as a source in Airbyte. ### Facebook App #### If you don't have a Facebook App -Visit the [Facebook Developers App hub](https://developers.facebook.com/apps/) and create an App and choose "Manage Business Integrations" as the purpose of the app. Fill out the remaining fields to create your app, then follow along the "Enable the Marketing API for your app" section. +Visit the [Facebook Developers App hub](https://developers.facebook.com/apps/) and create an App and choose "Manage Business Integrations" as the purpose of the app. Fill out the remaining fields to create your app, then follow along the "Enable the Marketing API for your app" section. #### Enable the Marketing API for your app -From the App's Dashboard screen \(seen in the screenshot below\) enable the Marketing API for your app if it is not already setup. +From the App's Dashboard screen \(seen in the screenshot below\) enable the Marketing API for your app if it is not already setup. -![](../../.gitbook/assets/screen-shot-2020-11-03-at-9.25.21-pm.png) +![](../../.gitbook/assets/screen-shot-2020-11-03-at-9.25.21-pm%20%281%29.png) ### API Access Token -In the App Dashboard screen, click Marketing API --> Tools on the left sidebar. Then highlight all the available token permissions \(`ads_management`, `ads_read`, `read_insights`\) and click "Get token". A long string of characters should appear in front of you; **this is the access token.** Copy this string for use in the Airbyte UI later. - -![](../../.gitbook/assets/screen-shot-2020-11-03-at-9.35.40-pm.png) - +In the App Dashboard screen, click Marketing API --> Tools on the left sidebar. Then highlight all the available token permissions \(`ads_management`, `ads_read`, `read_insights`\) and click "Get token". A long string of characters should appear in front of you; **this is the access token.** Copy this string for use in the Airbyte UI later. +![](../../.gitbook/assets/screen-shot-2020-11-03-at-9.35.40-pm%20%281%29.png) With the Ad Account ID and API access token, you should be ready to start pulling data from the Facebook Marketing API. Head to the Airbyte UI to setup your source connector! diff --git a/docs/integrations/sources/google-adwords.md b/docs/integrations/sources/google-adwords.md index e3d8f09cee707..15c54cede8171 100644 --- a/docs/integrations/sources/google-adwords.md +++ b/docs/integrations/sources/google-adwords.md @@ -27,17 +27,19 @@ This source is constrained by whatever API limits are set for the Google Adwords ### Requirements -* Google Adwords Manager Account with an approved Developer Token (note: In order to get API access to Google Adwords, you must have a "manager" account. This must be created separately from your standard account. You can find more information about this distinction in the [google ads docs](https://ads.google.com/home/tools/manager-accounts/).) +* Google Adwords Manager Account with an approved Developer Token \(note: In order to get API access to Google Adwords, you must have a "manager" account. This must be created separately from your standard account. You can find more information about this distinction in the [google ads docs](https://ads.google.com/home/tools/manager-accounts/).\) ### Setup guide This guide will provide information as if starting from scratch. Please skip over any steps you have already completed. + * Create an Adwords Account. Here are [Google's instruction](https://support.google.com/google-ads/answer/6366720) on how to create one. * Create an Adwords MANAGER Account. Here are [Google's instruction](https://ads.google.com/home/tools/manager-accounts/) on how to create one. * You should now have two Google Ads accounts: a normal account and a manager account. Link the Manager account to the normal account following [Google's documentation](https://support.google.com/google-ads/answer/7459601). * Apply for a developer token on your Manager account. This token allows you to access your data from the Google Ads API. Here are [Google's instructions](https://developers.google.com/google-ads/api/docs/first-call/dev-token). The docs are a little unclear on this point, but you will _not_ be able to access your data via the Google Ads API until this token is approved. You cannot use a test developer token, it has to be at least a basic developer token. It usually takes Google 24 hours to respond to these applications. - * This is the value you will use in the `developer_token` field. + * This is the value you will use in the `developer_token` field. * Fetch your `client_id`, `client_secret`, and `refresh_token`. Google provides [instructions](https://developers.google.com/adwords/api/docs/guides/first-api-call#set_up_oauth2_authentication) on how to do this. * Select your `customer_ids`. The `customer_ids` refer to the id of each of your Google Ads accounts. This is the 10 digit number in the top corner of the page when you are in google ads ui. The source will only pull data from the accounts for which you provide an id. If you are having trouble finding it, check out [Google's instructions](https://support.google.com/google-ads/answer/1704344). -Wow! That was a lot of steps. We are working on making the OAuth flow for all of our connectors simpler (allowing you to skip needing to get a `developer_token` and a `refresh_token` which are the most painful / time-consuming steps in this walkthrough). +Wow! That was a lot of steps. We are working on making the OAuth flow for all of our connectors simpler \(allowing you to skip needing to get a `developer_token` and a `refresh_token` which are the most painful / time-consuming steps in this walkthrough\). + diff --git a/docs/integrations/sources/google-sheets.md b/docs/integrations/sources/google-sheets.md index f6c3157f3cdd3..0dfb4a9fb9f37 100644 --- a/docs/integrations/sources/google-sheets.md +++ b/docs/integrations/sources/google-sheets.md @@ -72,7 +72,7 @@ Once you've created the Service Account, you need to explicitly give it access t Finally, you'll need the ID of the Spreadsheet you'd like to sync. To get it, navigate to the spreadsheet in your browser, then copy the portion of the URL which comes after "/d" and before "/edit" or "/view". This is the highlighted portion of the screenshot below: -![](../../.gitbook/assets/screen-shot-2020-10-30-at-2.44.55-pm%20%281%29%20%281%29.png) +![](../../.gitbook/assets/screen-shot-2020-10-30-at-2.44.55-pm%20%281%29%20%281%29%20%281%29.png) ### Setting up in the Airbyte UI diff --git a/tools/bin/ci_credentials.sh b/tools/bin/ci_credentials.sh index f7ccff14f54c9..5a38bee2101f2 100755 --- a/tools/bin/ci_credentials.sh +++ b/tools/bin/ci_credentials.sh @@ -34,3 +34,7 @@ echo "$ADWORDS_INTEGRATION_TEST_CREDS" > airbyte-integrations/connectors/source- FB_SECRETS_DIR=airbyte-integrations/connectors/source-facebook-marketing-api-singer/secrets mkdir $FB_SECRETS_DIR echo "$FACEBOOK_MARKETING_API_TEST_INTEGRATION_CREDS" > "${FB_SECRETS_DIR}/config.json" + +MKTO_SECRETS_DIR=airbyte-integrations/connectors/source-marketo-singer/secrets +mkdir $MKTO_SECRETS_DIR +echo "$SOURCE_MARKETO_SINGER_INTEGRATION_TEST_CONFIG" > "${MKTO_SECRETS_DIR}/config.json" diff --git a/tools/bin/standard_test_pr.sh b/tools/bin/standard_test_pr.sh index 55dd939a53043..3b3baaa7d0402 100755 --- a/tools/bin/standard_test_pr.sh +++ b/tools/bin/standard_test_pr.sh @@ -7,4 +7,5 @@ set -e assert_root ./gradlew --no-daemon --scan -x generateProtocolClassFiles \ - :airbyte-integrations:connectors:source-github-singer:standardSourceTestPython + :airbyte-integrations:connectors:source-github-singer:standardSourceTestPython \ + :airbyte-integrations:connectors:source-marketo-singer:standardSourceTestPython From 73ce71b385a4ca8179c8d0c709e39fc3bbab39eb Mon Sep 17 00:00:00 2001 From: Sherif Nada Date: Thu, 5 Nov 2020 22:36:11 -0800 Subject: [PATCH 13/18] merge master part 2 --- .../source-python/.gitignore.hbs | 1 + .../source-python/.secrets/config.json.hbs | 4 + .../source-singer/.gitignore.hbs | 1 + .../source-singer/.secrets/config.json.hbs | 4 + .../source-marketo-singer/.dockerignore | 7 + .../source-marketo-singer/.gitignore | 1 + .../source-marketo-singer/Dockerfile | 18 + .../source-marketo-singer/Dockerfile.test | 23 + .../source-marketo-singer/README.md | 62 + .../source-marketo-singer/airbyte_protocol | 1 + .../source-marketo-singer/base_python | 1 + .../source-marketo-singer/base_singer | 1 + .../source-marketo-singer/build.gradle | 34 + .../integration_tests/__init__.py | 27 + .../integration_tests/catalog.json | 47 + .../integration_tests/standard_source_test.py | 51 + .../source-marketo-singer/main_dev.py | 32 + .../source-marketo-singer/requirements.txt | 5 + .../sample_files/full_catalog.json | 2290 ++++++ .../sample_files/sample_catalog.json | 47 + .../connectors/source-marketo-singer/setup.py | 44 + .../source-marketo-singer/singer_catalog.json | 6401 +++++++++++++++++ .../singer_rendered_catalog.json | 3952 ++++++++++ .../source_marketo_singer/__init__.py | 27 + .../source_marketo_singer/source.py | 52 + .../source_marketo_singer/spec.json | 40 + .../unit_tests/unit_test.py | 44 + ...reen-shot-2020-11-03-at-9.25.21-pm (1).png | Bin 0 -> 260195 bytes ...reen-shot-2020-11-03-at-9.35.40-pm (1).png | Bin 0 -> 157176 bytes docs/integrations/sources/marketo.md | 88 + 30 files changed, 13305 insertions(+) create mode 100644 airbyte-integrations/connector-templates/source-python/.gitignore.hbs create mode 100644 airbyte-integrations/connector-templates/source-python/.secrets/config.json.hbs create mode 100644 airbyte-integrations/connector-templates/source-singer/.gitignore.hbs create mode 100644 airbyte-integrations/connector-templates/source-singer/.secrets/config.json.hbs create mode 100644 airbyte-integrations/connectors/source-marketo-singer/.dockerignore create mode 100644 airbyte-integrations/connectors/source-marketo-singer/.gitignore create mode 100644 airbyte-integrations/connectors/source-marketo-singer/Dockerfile create mode 100644 airbyte-integrations/connectors/source-marketo-singer/Dockerfile.test create mode 100644 airbyte-integrations/connectors/source-marketo-singer/README.md create mode 120000 airbyte-integrations/connectors/source-marketo-singer/airbyte_protocol create mode 120000 airbyte-integrations/connectors/source-marketo-singer/base_python create mode 120000 airbyte-integrations/connectors/source-marketo-singer/base_singer create mode 100644 airbyte-integrations/connectors/source-marketo-singer/build.gradle create mode 100644 airbyte-integrations/connectors/source-marketo-singer/integration_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-marketo-singer/integration_tests/catalog.json create mode 100644 airbyte-integrations/connectors/source-marketo-singer/integration_tests/standard_source_test.py create mode 100644 airbyte-integrations/connectors/source-marketo-singer/main_dev.py create mode 100644 airbyte-integrations/connectors/source-marketo-singer/requirements.txt create mode 100644 airbyte-integrations/connectors/source-marketo-singer/sample_files/full_catalog.json create mode 100644 airbyte-integrations/connectors/source-marketo-singer/sample_files/sample_catalog.json create mode 100644 airbyte-integrations/connectors/source-marketo-singer/setup.py create mode 100644 airbyte-integrations/connectors/source-marketo-singer/singer_catalog.json create mode 100644 airbyte-integrations/connectors/source-marketo-singer/singer_rendered_catalog.json create mode 100644 airbyte-integrations/connectors/source-marketo-singer/source_marketo_singer/__init__.py create mode 100644 airbyte-integrations/connectors/source-marketo-singer/source_marketo_singer/source.py create mode 100644 airbyte-integrations/connectors/source-marketo-singer/source_marketo_singer/spec.json create mode 100644 airbyte-integrations/connectors/source-marketo-singer/unit_tests/unit_test.py create mode 100644 docs/.gitbook/assets/screen-shot-2020-11-03-at-9.25.21-pm (1).png create mode 100644 docs/.gitbook/assets/screen-shot-2020-11-03-at-9.35.40-pm (1).png create mode 100644 docs/integrations/sources/marketo.md diff --git a/airbyte-integrations/connector-templates/source-python/.gitignore.hbs b/airbyte-integrations/connector-templates/source-python/.gitignore.hbs new file mode 100644 index 0000000000000..29fffc6a50cc9 --- /dev/null +++ b/airbyte-integrations/connector-templates/source-python/.gitignore.hbs @@ -0,0 +1 @@ +NEW_SOURCE_CHECKLIST.md diff --git a/airbyte-integrations/connector-templates/source-python/.secrets/config.json.hbs b/airbyte-integrations/connector-templates/source-python/.secrets/config.json.hbs new file mode 100644 index 0000000000000..5685aefceea27 --- /dev/null +++ b/airbyte-integrations/connector-templates/source-python/.secrets/config.json.hbs @@ -0,0 +1,4 @@ +{ + // TODO populate with needed configuration for integration tests or delete this file and any references to it + // the schema of this file should match what is in your spec.json +} diff --git a/airbyte-integrations/connector-templates/source-singer/.gitignore.hbs b/airbyte-integrations/connector-templates/source-singer/.gitignore.hbs new file mode 100644 index 0000000000000..29fffc6a50cc9 --- /dev/null +++ b/airbyte-integrations/connector-templates/source-singer/.gitignore.hbs @@ -0,0 +1 @@ +NEW_SOURCE_CHECKLIST.md diff --git a/airbyte-integrations/connector-templates/source-singer/.secrets/config.json.hbs b/airbyte-integrations/connector-templates/source-singer/.secrets/config.json.hbs new file mode 100644 index 0000000000000..d57d43390d422 --- /dev/null +++ b/airbyte-integrations/connector-templates/source-singer/.secrets/config.json.hbs @@ -0,0 +1,4 @@ +{ + // TODO populate with needed configuration for integration tests or delete this file and any references to it + // the schema of this file should match what is in your spec.json +} diff --git a/airbyte-integrations/connectors/source-marketo-singer/.dockerignore b/airbyte-integrations/connectors/source-marketo-singer/.dockerignore new file mode 100644 index 0000000000000..f16df8c3006c1 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/.dockerignore @@ -0,0 +1,7 @@ +* +!Dockerfile +!Dockerfile.test +!source_marketo_singer +!setup.py +!integration_tests +!secrets diff --git a/airbyte-integrations/connectors/source-marketo-singer/.gitignore b/airbyte-integrations/connectors/source-marketo-singer/.gitignore new file mode 100644 index 0000000000000..29fffc6a50cc9 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/.gitignore @@ -0,0 +1 @@ +NEW_SOURCE_CHECKLIST.md diff --git a/airbyte-integrations/connectors/source-marketo-singer/Dockerfile b/airbyte-integrations/connectors/source-marketo-singer/Dockerfile new file mode 100644 index 0000000000000..18a77437a1d36 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/Dockerfile @@ -0,0 +1,18 @@ +FROM airbyte/integration-base-singer:dev + +# Bash is installed for more convenient debugging. +# GCC is needed for CISO8601, a dependency of tap-marketo. +# See https://github.com/closeio/ciso8601/issues/98 for more information. +RUN apt-get update && apt-get install -y bash gcc && rm -rf /var/lib/apt/lists/* + +ENV CODE_PATH="source_marketo_singer" +ENV AIRBYTE_IMPL_MODULE="source_marketo_singer" +ENV AIRBYTE_IMPL_PATH="SourceMarketoSinger" + +WORKDIR /airbyte/integration_code +COPY $CODE_PATH ./$CODE_PATH +COPY setup.py ./ +RUN pip install ".[main]" + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-marketo-singer diff --git a/airbyte-integrations/connectors/source-marketo-singer/Dockerfile.test b/airbyte-integrations/connectors/source-marketo-singer/Dockerfile.test new file mode 100644 index 0000000000000..2b84eb90f991d --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/Dockerfile.test @@ -0,0 +1,23 @@ +FROM airbyte/base-python-test:dev + +# Bash is installed for convenient debugging. +RUN apt-get update && apt-get install -y bash && rm -rf /var/lib/apt/lists/* + +ENV MODULE_NAME="source_marketo_singer" +ENV CODE_PATH="integration_tests" +ENV AIRBYTE_TEST_MODULE="integration_tests" +ENV AIRBYTE_TEST_PATH="SourceMarketoSingerStandardTest" + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-marketo-singer-standard-test + +WORKDIR /airbyte/integration_code +COPY $MODULE_NAME $MODULE_NAME +COPY $CODE_PATH $CODE_PATH +COPY secrets/* $CODE_PATH/ +COPY $MODULE_NAME/*.json $CODE_PATH +COPY setup.py ./ + +RUN pip install ".[tests]" + +WORKDIR /airbyte diff --git a/airbyte-integrations/connectors/source-marketo-singer/README.md b/airbyte-integrations/connectors/source-marketo-singer/README.md new file mode 100644 index 0000000000000..adf00a300a1f3 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/README.md @@ -0,0 +1,62 @@ +# Source Marketo Singer + +This is the repository for the Marketo source connector, based on a Singer tap. +For information about how to use this connector within Airbyte, see [the User Documentation](https://docs.airbyte.io/integrations/sources/marketo). + +## Local development +### Prerequisites +**To iterate on this connector, make sure to complete this prerequisites section.** + +#### Build & Activate Virtual Environment +First, build the module by running the following from the `airbyte` project root directory: +``` +./gradlew :airbyte-integrations:connectors:source-marketo-singer:build +``` + +This will generate a virtualenv for this module in `source-marketo-singer/.venv`. Make sure this venv is active in your +development environment of choice. To activate the venv from the terminal, run: +``` +cd airbyte-integrations/connectors/source-marketo # cd into the connector directory +source .venv/bin/activate +``` +If you are in an IDE, follow your IDE's instructions to activate the virtualenv. + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/marketo) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_marketo_singer/spec.json` file. +See `sample_files/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in RPass under the secret name `source-marketo-singer-integration-test-config` +and place them into `secrets/config.json`. + +### Locally running the connector +``` +python main_dev.py spec +python main_dev.py check --config secrets/config.json +python main_dev.py discover --config secrets/config.json +python main_dev.py read --config secrets/config.json --catalog sample_files/sample_catalog.json +``` + +### Unit Tests +To run unit tests locally, from the connector root run: +``` +pytest unit_tests +``` + +### Locally running the connector docker image +``` +# in airbyte root directory +./gradlew :airbyte-integrations:connectors:source-marketo-singer:buildImage +docker run --rm airbyte/source-marketo-singer:dev spec +docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-marketo-singer/secrets:/secrets airbyte/source-marketo-singer:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-marketo-singer/secrets:/secrets airbyte/source-marketo-singer:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/airbyte-integrations/connectors/source-marketo-singer/secrets:/secrets -v $(pwd)/airbyte-integrations/connectors/source-marketo-singer/sample_files:/sample_files airbyte/source-marketo-singer:dev read --config /secrets/config.json --catalog /sample_files/sample_catalog.json +``` + +### Integration Tests +1. From the airbyte project root, run `./gradlew :airbyte-integrations:connectors:source-marketo-singer:standardSourceTestPython` to run the standard integration test suite. +1. To run additional integration tests, place your integration tests in the `integration_tests` directory and run them with `pytest integration_tests`. + Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. + +## Dependency Management +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. diff --git a/airbyte-integrations/connectors/source-marketo-singer/airbyte_protocol b/airbyte-integrations/connectors/source-marketo-singer/airbyte_protocol new file mode 120000 index 0000000000000..66a1be0f906fb --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/airbyte_protocol @@ -0,0 +1 @@ +../../bases/airbyte-protocol/airbyte_protocol \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-marketo-singer/base_python b/airbyte-integrations/connectors/source-marketo-singer/base_python new file mode 120000 index 0000000000000..ebf099aa6bdf9 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/base_python @@ -0,0 +1 @@ +../../bases/base-python/base_python \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-marketo-singer/base_singer b/airbyte-integrations/connectors/source-marketo-singer/base_singer new file mode 120000 index 0000000000000..873ffcbc1ac36 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/base_singer @@ -0,0 +1 @@ +../../bases/base-singer/base_singer \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-marketo-singer/build.gradle b/airbyte-integrations/connectors/source-marketo-singer/build.gradle new file mode 100644 index 0000000000000..d9df3e793bfed --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/build.gradle @@ -0,0 +1,34 @@ +project.ext.pyModule = 'source_marketo_singer' +apply from: rootProject.file('tools/gradle/commons/integrations/python.gradle') +apply from: rootProject.file('tools/gradle/commons/integrations/image.gradle') +apply from: rootProject.file('tools/gradle/commons/integrations/test-image.gradle') +apply from: rootProject.file('tools/gradle/commons/integrations/integration-test.gradle') +apply from: rootProject.file('tools/gradle/commons/integrations/standard-source-test-python.gradle') + + +standardSourceTestPython { + ext { + imageName = "${extractImageName(project.file('Dockerfile'))}:dev" + pythonContainerName = "${extractImageName(project.file('Dockerfile.test'))}:dev" + } +} + +task installTestDeps(type: PythonTask){ + module = "pip" + command = "install .[tests]" +} + +task unitTest(type: PythonTask){ + module = "pytest" + command = "unit_tests" +} + +unitTest.dependsOn(installTestDeps) +build.dependsOn(unitTest) +build.dependsOn ':airbyte-integrations:bases:base-python-test:build' + +buildImage.dependsOn ':airbyte-integrations:bases:base-singer:buildImage' +buildTestImage.dependsOn ':airbyte-integrations:bases:base-python-test:buildImage' + +integrationTest.dependsOn(buildImage) +standardSourceTestPython.dependsOn(buildTestImage) diff --git a/airbyte-integrations/connectors/source-marketo-singer/integration_tests/__init__.py b/airbyte-integrations/connectors/source-marketo-singer/integration_tests/__init__.py new file mode 100644 index 0000000000000..7a6b6ad4b2d05 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/integration_tests/__init__.py @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2020 Airbyte + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from .standard_source_test import SourceMarketoSingerStandardTest + +__all__ = ["SourceMarketoSingerStandardTest"] diff --git a/airbyte-integrations/connectors/source-marketo-singer/integration_tests/catalog.json b/airbyte-integrations/connectors/source-marketo-singer/integration_tests/catalog.json new file mode 100644 index 0000000000000..23c0314081dc2 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/integration_tests/catalog.json @@ -0,0 +1,47 @@ +{ + "streams": [ + { + "name": "activity_types", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": ["string", "null"] + }, + "primaryAttribute": { + "type": ["object", "null"], + "properties": { + "name": { + "type": "string" + }, + "dataType": { + "type": "string" + } + } + }, + "attributes": { + "type": ["array", "null"], + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "dataType": { + "type": "string" + } + } + } + } + } + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-marketo-singer/integration_tests/standard_source_test.py b/airbyte-integrations/connectors/source-marketo-singer/integration_tests/standard_source_test.py new file mode 100644 index 0000000000000..ab757811839e7 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/integration_tests/standard_source_test.py @@ -0,0 +1,51 @@ +""" +MIT License + +Copyright (c) 2020 Airbyte + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import json +import pkgutil + +from airbyte_protocol import AirbyteCatalog, ConnectorSpecification +from base_python_test import StandardSourceTestIface + + +class SourceMarketoSingerStandardTest(StandardSourceTestIface): + def __init__(self): + pass + + def get_spec(self) -> ConnectorSpecification: + raw_spec = pkgutil.get_data(self.__class__.__module__.split(".")[0], "spec.json") + return ConnectorSpecification.parse_obj(json.loads(raw_spec)) + + def get_config(self) -> object: + return json.loads(pkgutil.get_data(self.__class__.__module__.split(".")[0], "config.json")) + + def get_catalog(self) -> AirbyteCatalog: + raw_catalog = pkgutil.get_data(self.__class__.__module__.split(".")[0], "catalog.json") + return AirbyteCatalog.parse_obj(json.loads(raw_catalog)) + + def setup(self) -> None: + pass + + def teardown(self) -> None: + pass diff --git a/airbyte-integrations/connectors/source-marketo-singer/main_dev.py b/airbyte-integrations/connectors/source-marketo-singer/main_dev.py new file mode 100644 index 0000000000000..39c993d48823a --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/main_dev.py @@ -0,0 +1,32 @@ +""" +MIT License + +Copyright (c) 2020 Airbyte + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import sys + +from base_python.entrypoint import launch +from source_marketo_singer import SourceMarketoSinger + +if __name__ == "__main__": + source = SourceMarketoSinger() + launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-marketo-singer/requirements.txt b/airbyte-integrations/connectors/source-marketo-singer/requirements.txt new file mode 100644 index 0000000000000..010a706039fa2 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/requirements.txt @@ -0,0 +1,5 @@ +-e ../../bases/airbyte-protocol +-e ../../bases/base-singer +-e ../../bases/base-python +-e ../../bases/base-python-test +-e . diff --git a/airbyte-integrations/connectors/source-marketo-singer/sample_files/full_catalog.json b/airbyte-integrations/connectors/source-marketo-singer/sample_files/full_catalog.json new file mode 100644 index 0000000000000..f7b87d00dfc82 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/sample_files/full_catalog.json @@ -0,0 +1,2290 @@ +{ + "streams": [ + { + "name": "leads", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "company": { + "type": ["string", "null"] + }, + "site": { + "type": ["string", "null"] + }, + "billingStreet": { + "type": ["string", "null"] + }, + "billingCity": { + "type": ["string", "null"] + }, + "billingState": { + "type": ["string", "null"] + }, + "billingCountry": { + "type": ["string", "null"] + }, + "billingPostalCode": { + "type": ["string", "null"] + }, + "website": { + "type": ["string", "null"] + }, + "mainPhone": { + "type": ["string", "null"] + }, + "annualRevenue": { + "type": ["number", "null"] + }, + "numberOfEmployees": { + "type": ["integer", "null"] + }, + "industry": { + "type": ["string", "null"] + }, + "sicCode": { + "type": ["string", "null"] + }, + "mktoCompanyNotes": { + "type": ["string", "null"] + }, + "externalCompanyId": { + "type": ["string", "null"] + }, + "id": { + "type": "integer" + }, + "mktoName": { + "type": ["string", "null"] + }, + "personType": { + "type": ["string", "null"] + }, + "mktoIsPartner": { + "type": ["boolean", "null"] + }, + "isLead": { + "type": ["boolean", "null"] + }, + "mktoIsCustomer": { + "type": ["boolean", "null"] + }, + "isAnonymous": { + "type": ["boolean", "null"] + }, + "salutation": { + "type": ["string", "null"] + }, + "firstName": { + "type": ["string", "null"] + }, + "middleName": { + "type": ["string", "null"] + }, + "lastName": { + "type": ["string", "null"] + }, + "email": { + "type": ["string", "null"] + }, + "phone": { + "type": ["string", "null"] + }, + "mobilePhone": { + "type": ["string", "null"] + }, + "fax": { + "type": ["string", "null"] + }, + "title": { + "type": ["string", "null"] + }, + "contactCompany": { + "type": ["string", "null"] + }, + "dateOfBirth": { + "type": ["string", "null"], + "format": "date-time" + }, + "address": { + "type": ["string", "null"] + }, + "city": { + "type": ["string", "null"] + }, + "state": { + "type": ["string", "null"] + }, + "country": { + "type": ["string", "null"] + }, + "postalCode": { + "type": ["string", "null"] + }, + "personTimeZone": { + "type": ["string", "null"] + }, + "originalSourceType": { + "type": ["string", "null"] + }, + "originalSourceInfo": { + "type": ["string", "null"] + }, + "registrationSourceType": { + "type": ["string", "null"] + }, + "registrationSourceInfo": { + "type": ["string", "null"] + }, + "originalSearchEngine": { + "type": ["string", "null"] + }, + "originalSearchPhrase": { + "type": ["string", "null"] + }, + "originalReferrer": { + "type": ["string", "null"] + }, + "emailInvalid": { + "type": ["boolean", "null"] + }, + "emailInvalidCause": { + "type": ["string", "null"] + }, + "unsubscribed": { + "type": ["boolean", "null"] + }, + "unsubscribedReason": { + "type": ["string", "null"] + }, + "doNotCall": { + "type": ["boolean", "null"] + }, + "mktoDoNotCallCause": { + "type": ["string", "null"] + }, + "doNotCallReason": { + "type": ["string", "null"] + }, + "marketingSuspended": { + "type": ["boolean", "null"] + }, + "marketingSuspendedCause": { + "type": ["string", "null"] + }, + "blackListed": { + "type": ["boolean", "null"] + }, + "blackListedCause": { + "type": ["string", "null"] + }, + "mktoPersonNotes": { + "type": ["string", "null"] + }, + "anonymousIP": { + "type": ["string", "null"] + }, + "inferredCompany": { + "type": ["string", "null"] + }, + "inferredCountry": { + "type": ["string", "null"] + }, + "inferredCity": { + "type": ["string", "null"] + }, + "inferredStateRegion": { + "type": ["string", "null"] + }, + "inferredPostalCode": { + "type": ["string", "null"] + }, + "inferredMetropolitanArea": { + "type": ["string", "null"] + }, + "inferredPhoneAreaCode": { + "type": ["string", "null"] + }, + "emailSuspended": { + "type": ["boolean", "null"] + }, + "emailSuspendedCause": { + "type": ["string", "null"] + }, + "emailSuspendedAt": { + "type": ["string", "null"], + "format": "date-time" + }, + "department": { + "type": ["string", "null"] + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "cookies": { + "type": ["string", "null"] + }, + "externalSalesPersonId": { + "type": ["string", "null"] + }, + "leadPerson": { + "type": ["string", "null"] + }, + "leadRole": { + "type": ["string", "null"] + }, + "leadSource": { + "type": ["string", "null"] + }, + "leadStatus": { + "type": ["string", "null"] + }, + "leadScore": { + "type": ["integer", "null"] + }, + "urgency": { + "type": ["number", "null"] + }, + "priority": { + "type": ["integer", "null"] + }, + "relativeScore": { + "type": ["integer", "null"] + }, + "relativeUrgency": { + "type": ["integer", "null"] + }, + "rating": { + "type": ["string", "null"] + }, + "personPrimaryLeadInterest": { + "type": ["string", "null"] + }, + "leadPartitionId": { + "type": ["string", "null"] + }, + "leadRevenueCycleModelId": { + "type": ["string", "null"] + }, + "leadRevenueStageId": { + "type": ["string", "null"] + }, + "acquisitionProgramId": { + "type": ["string", "null"] + }, + "mktoAcquisitionDate": { + "type": ["string", "null"], + "format": "date-time" + } + } + } + }, + { + "name": "activity_types", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": ["string", "null"] + }, + "primaryAttribute": { + "type": ["object", "null"], + "properties": { + "name": { + "type": "string" + }, + "dataType": { + "type": "string" + } + } + }, + "attributes": { + "type": ["array", "null"], + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "dataType": { + "type": "string" + } + } + } + } + } + } + }, + { + "name": "activities_visit_webpage", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "client_ip_address": { + "type": ["string", "null"] + }, + "query_parameters": { + "type": ["string", "null"] + }, + "referrer_url": { + "type": ["string", "null"] + }, + "search_engine": { + "type": ["string", "null"] + }, + "search_query": { + "type": ["string", "null"] + }, + "user_agent": { + "type": ["string", "null"] + }, + "webpage_url": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_fill_out_form", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "client_ip_address": { + "type": ["string", "null"] + }, + "form_fields": { + "type": ["string", "null"] + }, + "query_parameters": { + "type": ["string", "null"] + }, + "referrer_url": { + "type": ["string", "null"] + }, + "user_agent": { + "type": ["string", "null"] + }, + "webpage_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_click_link", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "client_ip_address": { + "type": ["string", "null"] + }, + "query_parameters": { + "type": ["string", "null"] + }, + "referrer_url": { + "type": ["string", "null"] + }, + "user_agent": { + "type": ["string", "null"] + }, + "webpage_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_send_email", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "step_id": { + "type": ["integer", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_email_delivered", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "step_id": { + "type": ["integer", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_email_bounced", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "category": { + "type": ["string", "null"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "details": { + "type": ["string", "null"] + }, + "email": { + "type": ["string", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "step_id": { + "type": ["integer", "null"] + }, + "subcategory": { + "type": ["string", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_unsubscribe_email", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["string", "null"] + }, + "client_ip_address": { + "type": ["string", "null"] + }, + "form_fields": { + "type": ["string", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "query_parameters": { + "type": ["string", "null"] + }, + "referrer_url": { + "type": ["string", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + }, + "user_agent": { + "type": ["string", "null"] + }, + "webform_id": { + "type": ["integer", "null"] + }, + "webpage_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_open_email", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "device": { + "type": ["string", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "is_mobile_device": { + "type": ["boolean", "null"] + }, + "platform": { + "type": ["string", "null"] + }, + "step_id": { + "type": ["integer", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + }, + "user_agent": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_click_email", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "device": { + "type": ["string", "null"] + }, + "is_mobile_device": { + "type": ["boolean", "null"] + }, + "is_predictive": { + "type": ["boolean", "null"] + }, + "link": { + "type": ["string", "null"] + }, + "link_id": { + "type": ["string", "null"] + }, + "platform": { + "type": ["string", "null"] + }, + "step_id": { + "type": ["integer", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + }, + "user_agent": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_new_lead", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "created_date": { + "type": ["string", "null"], + "format": "date-time" + }, + "form_name": { + "type": ["string", "null"] + }, + "lead_source": { + "type": ["string", "null"] + }, + "list_name": { + "type": ["string", "null"] + }, + "sfdc_type": { + "type": ["string", "null"] + }, + "source_type": { + "type": ["string", "null"] + }, + "api_method_name": { + "type": ["string", "null"] + }, + "modifying_user": { + "type": ["string", "null"] + }, + "request_id": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_change_data_value", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "new_value": { + "type": ["string", "null"] + }, + "old_value": { + "type": ["string", "null"] + }, + "reason": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + }, + "api_method_name": { + "type": ["string", "null"] + }, + "modifying_user": { + "type": ["string", "null"] + }, + "request_id": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_change_score", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "change_value": { + "type": ["string", "null"] + }, + "new_value": { + "type": ["integer", "null"] + }, + "old_value": { + "type": ["integer", "null"] + }, + "reason": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_add_to_list", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "source": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_remove_from_list", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "source": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_email_bounced_soft", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "category": { + "type": ["string", "null"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "details": { + "type": ["string", "null"] + }, + "email": { + "type": ["string", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "step_id": { + "type": ["integer", "null"] + }, + "subcategory": { + "type": ["string", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_merge_leads", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "merge_ids": { + "type": ["array", "null"], + "items": { + "type": ["integer", "number", "string", "null"] + } + }, + "master_updated": { + "type": ["boolean", "null"] + }, + "merged_in_sales": { + "type": ["boolean", "null"] + }, + "merge_source": { + "type": ["string", "null"] + }, + "api_method_name": { + "type": ["string", "null"] + }, + "modifying_user": { + "type": ["string", "null"] + }, + "request_id": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_add_to_opportunity", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "is_primary": { + "type": ["boolean", "null"] + }, + "role": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_remove_from_opportunity", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "is_primary": { + "type": ["boolean", "null"] + }, + "role": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_update_opportunity", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "attribute_name": { + "type": ["string", "null"] + }, + "data_value_changes": { + "type": ["string", "null"] + }, + "new_value": { + "type": ["string", "null"] + }, + "old_value": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_delete_lead", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "remove_from_crm": { + "type": ["boolean", "null"] + }, + "api_method_name": { + "type": ["string", "null"] + }, + "modifying_user": { + "type": ["string", "null"] + }, + "request_id": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_send_alert", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "send_to_list": { + "type": ["string", "null"] + }, + "send_to_owner": { + "type": ["string", "null"] + }, + "send_to_smart_list": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_send_sales_email", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "sent_by": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + }, + "template_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_open_sales_email", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "sent_by": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + }, + "template_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_click_sales_email", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "is_predictive": { + "type": ["boolean", "null"] + }, + "link": { + "type": ["string", "null"] + }, + "sent_by": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + }, + "template_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_receive_sales_email", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "received_by": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_request_campaign", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "source": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_sales_email_bounced", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "category": { + "type": ["string", "null"] + }, + "details": { + "type": ["string", "null"] + }, + "email": { + "type": ["string", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "sent_by": { + "type": ["string", "null"] + }, + "subcategory": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_change_lead_partition", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "old_partition_id": { + "type": ["integer", "null"] + }, + "reason": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_change_revenue_stage", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "new_stage_id": { + "type": ["integer", "null"] + }, + "old_stage_id": { + "type": ["integer", "null"] + }, + "reason": { + "type": ["string", "null"] + }, + "sla_expiration": { + "type": ["string", "null"], + "format": "date-time" + }, + "new_stage": { + "type": ["string", "null"] + }, + "old_stage": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_change_revenue_stage_manually", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + } + } + } + }, + { + "name": "activities_change_status_in_progression", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "acquired_by": { + "type": ["boolean", "null"] + }, + "new_status_id": { + "type": ["integer", "null"] + }, + "old_status_id": { + "type": ["integer", "null"] + }, + "program_member_id": { + "type": ["integer", "null"] + }, + "reason": { + "type": ["string", "null"] + }, + "success": { + "type": ["boolean", "null"] + }, + "new_status": { + "type": ["string", "null"] + }, + "old_status": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_change_segment", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "new_segment_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_call_webhook", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "error_type": { + "type": ["integer", "null"] + }, + "response": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_sent_forward_to_friend_email", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "lead_id": { + "type": ["string", "null"] + }, + "step_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_received_forward_to_friend_email", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "lead_id": { + "type": ["string", "null"] + }, + "step_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_add_to_nurture", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "track_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_change_nurture_track", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "new_track_id": { + "type": ["integer", "null"] + }, + "previous_track_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "activities_change_nurture_cadence", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "new_nurture_cadence": { + "type": ["string", "null"] + }, + "previous_nurture_cadence": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_change_program_member_data", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "attribute_display_name": { + "type": ["string", "null"] + }, + "attribute_name": { + "type": ["integer", "null"] + }, + "new_value": { + "type": ["string", "null"] + }, + "old_value": { + "type": ["string", "null"] + }, + "reason": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_push_lead_to_marketo", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "reason": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + }, + "api_method_name": { + "type": ["string", "null"] + }, + "modifying_user": { + "type": ["string", "null"] + }, + "request_id": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "activities_share_content", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "share_message": { + "type": ["string", "null"] + }, + "social_app_type_id": { + "type": ["integer", "null"] + }, + "social_network": { + "type": ["string", "null"] + }, + "webpage_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "name": "campaigns", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "active": { + "type": ["boolean", "null"] + }, + "description": { + "type": ["string", "null"] + }, + "name": { + "type": "string" + }, + "programId": { + "type": ["integer", "null"] + }, + "programName": { + "type": ["string", "null"] + }, + "type": { + "type": "string" + }, + "workspaceName": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "lists", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "description": { + "type": ["string", "null"] + }, + "programName": { + "type": ["string", "null"] + }, + "workspaceName": { + "type": ["string", "null"] + } + } + } + }, + { + "name": "programs", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "name": { + "type": "string" + }, + "description": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "channel": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "workspace": { + "type": ["null", "string"] + }, + "folder": { + "type": "object", + "properties": { + "type": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "integer"] + }, + "folderName": { + "type": ["null", "string"] + } + } + } + } + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-marketo-singer/sample_files/sample_catalog.json b/airbyte-integrations/connectors/source-marketo-singer/sample_files/sample_catalog.json new file mode 100644 index 0000000000000..23c0314081dc2 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/sample_files/sample_catalog.json @@ -0,0 +1,47 @@ +{ + "streams": [ + { + "name": "activity_types", + "json_schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": ["string", "null"] + }, + "primaryAttribute": { + "type": ["object", "null"], + "properties": { + "name": { + "type": "string" + }, + "dataType": { + "type": "string" + } + } + }, + "attributes": { + "type": ["array", "null"], + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "dataType": { + "type": "string" + } + } + } + } + } + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-marketo-singer/setup.py b/airbyte-integrations/connectors/source-marketo-singer/setup.py new file mode 100644 index 0000000000000..237b076e4ea27 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/setup.py @@ -0,0 +1,44 @@ +""" +MIT License + +Copyright (c) 2020 Airbyte + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from setuptools import find_packages, setup + +setup( + name="source_marketo_singer", + description="Source implementation for Marketo, built on the Singer tap implementation.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=["airbyte-protocol"], + package_data={"": ["*.json"]}, + setup_requires=["pytest-runner"], + tests_require=["pytest"], + extras_require={ + # Dependencies required by the main package but not integration tests should go in main. Deps required by + # integration tests but not the main package go in integration_tests. Deps required by both should go in + # install_requires. + "main": ["base-singer", "base-python", "tap-marketo==2.4.1"], + "tests": ["airbyte_python_test", "pytest"], + }, +) diff --git a/airbyte-integrations/connectors/source-marketo-singer/singer_catalog.json b/airbyte-integrations/connectors/source-marketo-singer/singer_catalog.json new file mode 100644 index 0000000000000..5cb82befe7124 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/singer_catalog.json @@ -0,0 +1,6401 @@ +{ + "streams": [ + { + "tap_stream_id": "leads", + "stream": "leads", + "key_properties": ["id"], + "metadata": [ + { + "breadcrumb": ["properties", "company"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "site"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "billingStreet"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "billingCity"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "billingState"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "billingCountry"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "billingPostalCode"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "website"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "mainPhone"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "annualRevenue"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "numberOfEmployees"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "industry"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "sicCode"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "mktoCompanyNotes"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "externalCompanyId"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "mktoName"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "personType"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "mktoIsPartner"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "isLead"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "mktoIsCustomer"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "isAnonymous"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "salutation"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "firstName"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "middleName"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "lastName"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "email"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "phone"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "mobilePhone"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "fax"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "title"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "contactCompany"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "dateOfBirth"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "address"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "city"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "state"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "country"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "postalCode"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "personTimeZone"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "originalSourceType"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "originalSourceInfo"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "registrationSourceType"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "registrationSourceInfo"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "originalSearchEngine"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "originalSearchPhrase"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "originalReferrer"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "emailInvalid"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "emailInvalidCause"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "unsubscribed"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "unsubscribedReason"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "doNotCall"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "mktoDoNotCallCause"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "doNotCallReason"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "marketingSuspended"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "marketingSuspendedCause"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "blackListed"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "blackListedCause"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "mktoPersonNotes"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "anonymousIP"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "inferredCompany"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "inferredCountry"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "inferredCity"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "inferredStateRegion"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "inferredPostalCode"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "inferredMetropolitanArea"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "inferredPhoneAreaCode"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "emailSuspended"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "emailSuspendedCause"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "emailSuspendedAt"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "department"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "createdAt"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "updatedAt"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "cookies"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "externalSalesPersonId"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "leadPerson"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "leadRole"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "leadSource"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "leadStatus"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "leadScore"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "urgency"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "priority"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "relativeScore"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "relativeUrgency"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "rating"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "personPrimaryLeadInterest"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "leadPartitionId"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "leadRevenueCycleModelId"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "leadRevenueStageId"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "acquisitionProgramId"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "mktoAcquisitionDate"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": [], + "metadata": { + "table-key-properties": ["id"] + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "company": { + "type": ["string", "null"] + }, + "site": { + "type": ["string", "null"] + }, + "billingStreet": { + "type": ["string", "null"] + }, + "billingCity": { + "type": ["string", "null"] + }, + "billingState": { + "type": ["string", "null"] + }, + "billingCountry": { + "type": ["string", "null"] + }, + "billingPostalCode": { + "type": ["string", "null"] + }, + "website": { + "type": ["string", "null"] + }, + "mainPhone": { + "type": ["string", "null"] + }, + "annualRevenue": { + "type": ["number", "null"] + }, + "numberOfEmployees": { + "type": ["integer", "null"] + }, + "industry": { + "type": ["string", "null"] + }, + "sicCode": { + "type": ["string", "null"] + }, + "mktoCompanyNotes": { + "type": ["string", "null"] + }, + "externalCompanyId": { + "type": ["string", "null"] + }, + "id": { + "type": "integer" + }, + "mktoName": { + "type": ["string", "null"] + }, + "personType": { + "type": ["string", "null"] + }, + "mktoIsPartner": { + "type": ["boolean", "null"] + }, + "isLead": { + "type": ["boolean", "null"] + }, + "mktoIsCustomer": { + "type": ["boolean", "null"] + }, + "isAnonymous": { + "type": ["boolean", "null"] + }, + "salutation": { + "type": ["string", "null"] + }, + "firstName": { + "type": ["string", "null"] + }, + "middleName": { + "type": ["string", "null"] + }, + "lastName": { + "type": ["string", "null"] + }, + "email": { + "type": ["string", "null"] + }, + "phone": { + "type": ["string", "null"] + }, + "mobilePhone": { + "type": ["string", "null"] + }, + "fax": { + "type": ["string", "null"] + }, + "title": { + "type": ["string", "null"] + }, + "contactCompany": { + "type": ["string", "null"] + }, + "dateOfBirth": { + "type": ["string", "null"], + "format": "date-time" + }, + "address": { + "type": ["string", "null"] + }, + "city": { + "type": ["string", "null"] + }, + "state": { + "type": ["string", "null"] + }, + "country": { + "type": ["string", "null"] + }, + "postalCode": { + "type": ["string", "null"] + }, + "personTimeZone": { + "type": ["string", "null"] + }, + "originalSourceType": { + "type": ["string", "null"] + }, + "originalSourceInfo": { + "type": ["string", "null"] + }, + "registrationSourceType": { + "type": ["string", "null"] + }, + "registrationSourceInfo": { + "type": ["string", "null"] + }, + "originalSearchEngine": { + "type": ["string", "null"] + }, + "originalSearchPhrase": { + "type": ["string", "null"] + }, + "originalReferrer": { + "type": ["string", "null"] + }, + "emailInvalid": { + "type": ["boolean", "null"] + }, + "emailInvalidCause": { + "type": ["string", "null"] + }, + "unsubscribed": { + "type": ["boolean", "null"] + }, + "unsubscribedReason": { + "type": ["string", "null"] + }, + "doNotCall": { + "type": ["boolean", "null"] + }, + "mktoDoNotCallCause": { + "type": ["string", "null"] + }, + "doNotCallReason": { + "type": ["string", "null"] + }, + "marketingSuspended": { + "type": ["boolean", "null"] + }, + "marketingSuspendedCause": { + "type": ["string", "null"] + }, + "blackListed": { + "type": ["boolean", "null"] + }, + "blackListedCause": { + "type": ["string", "null"] + }, + "mktoPersonNotes": { + "type": ["string", "null"] + }, + "anonymousIP": { + "type": ["string", "null"] + }, + "inferredCompany": { + "type": ["string", "null"] + }, + "inferredCountry": { + "type": ["string", "null"] + }, + "inferredCity": { + "type": ["string", "null"] + }, + "inferredStateRegion": { + "type": ["string", "null"] + }, + "inferredPostalCode": { + "type": ["string", "null"] + }, + "inferredMetropolitanArea": { + "type": ["string", "null"] + }, + "inferredPhoneAreaCode": { + "type": ["string", "null"] + }, + "emailSuspended": { + "type": ["boolean", "null"] + }, + "emailSuspendedCause": { + "type": ["string", "null"] + }, + "emailSuspendedAt": { + "type": ["string", "null"], + "format": "date-time" + }, + "department": { + "type": ["string", "null"] + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "cookies": { + "type": ["string", "null"] + }, + "externalSalesPersonId": { + "type": ["string", "null"] + }, + "leadPerson": { + "type": ["string", "null"] + }, + "leadRole": { + "type": ["string", "null"] + }, + "leadSource": { + "type": ["string", "null"] + }, + "leadStatus": { + "type": ["string", "null"] + }, + "leadScore": { + "type": ["integer", "null"] + }, + "urgency": { + "type": ["number", "null"] + }, + "priority": { + "type": ["integer", "null"] + }, + "relativeScore": { + "type": ["integer", "null"] + }, + "relativeUrgency": { + "type": ["integer", "null"] + }, + "rating": { + "type": ["string", "null"] + }, + "personPrimaryLeadInterest": { + "type": ["string", "null"] + }, + "leadPartitionId": { + "type": ["string", "null"] + }, + "leadRevenueCycleModelId": { + "type": ["string", "null"] + }, + "leadRevenueStageId": { + "type": ["string", "null"] + }, + "acquisitionProgramId": { + "type": ["string", "null"] + }, + "mktoAcquisitionDate": { + "type": ["string", "null"], + "format": "date-time" + } + } + } + }, + { + "tap_stream_id": "activity_types", + "stream": "activity_types", + "key_properties": ["id"], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": ["string", "null"] + }, + "primaryAttribute": { + "type": ["object", "null"], + "properties": { + "name": { + "type": "string" + }, + "dataType": { + "type": "string" + } + } + }, + "attributes": { + "type": ["array", "null"], + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "dataType": { + "type": "string" + } + } + } + } + } + }, + "metadata": [ + { + "breadcrumb": ["properties", "id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "description"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "primaryAttribute"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "attributes"], + "metadata": { + "inclusion": "unsupported" + } + }, + { + "breadcrumb": [], + "metadata": { + "inclusion": "automatic", + "table-key-properties": ["id"] + } + } + ] + }, + { + "tap_stream_id": "activities_visit_webpage", + "stream": "activities_visit_webpage", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "webpage_id", + "marketo.activity-id": 1, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "client_ip_address"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "query_parameters"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "referrer_url"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "search_engine"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "search_query"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "user_agent"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "webpage_url"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "client_ip_address": { + "type": ["string", "null"] + }, + "query_parameters": { + "type": ["string", "null"] + }, + "referrer_url": { + "type": ["string", "null"] + }, + "search_engine": { + "type": ["string", "null"] + }, + "search_query": { + "type": ["string", "null"] + }, + "user_agent": { + "type": ["string", "null"] + }, + "webpage_url": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_fill_out_form", + "stream": "activities_fill_out_form", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "webform_id", + "marketo.activity-id": 2, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "client_ip_address"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "form_fields"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "query_parameters"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "referrer_url"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "user_agent"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "webpage_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "client_ip_address": { + "type": ["string", "null"] + }, + "form_fields": { + "type": ["string", "null"] + }, + "query_parameters": { + "type": ["string", "null"] + }, + "referrer_url": { + "type": ["string", "null"] + }, + "user_agent": { + "type": ["string", "null"] + }, + "webpage_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_click_link", + "stream": "activities_click_link", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "link_id", + "marketo.activity-id": 3, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "client_ip_address"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "query_parameters"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "referrer_url"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "user_agent"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "webpage_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "client_ip_address": { + "type": ["string", "null"] + }, + "query_parameters": { + "type": ["string", "null"] + }, + "referrer_url": { + "type": ["string", "null"] + }, + "user_agent": { + "type": ["string", "null"] + }, + "webpage_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_send_email", + "stream": "activities_send_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 6, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "step_id": { + "type": ["integer", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_email_delivered", + "stream": "activities_email_delivered", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 7, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "step_id": { + "type": ["integer", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_email_bounced", + "stream": "activities_email_bounced", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 8, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "category"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "details"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "email"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "subcategory"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "category": { + "type": ["string", "null"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "details": { + "type": ["string", "null"] + }, + "email": { + "type": ["string", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "step_id": { + "type": ["integer", "null"] + }, + "subcategory": { + "type": ["string", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_unsubscribe_email", + "stream": "activities_unsubscribe_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 9, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "client_ip_address"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "form_fields"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "query_parameters"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "referrer_url"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "user_agent"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "webform_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "webpage_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["string", "null"] + }, + "client_ip_address": { + "type": ["string", "null"] + }, + "form_fields": { + "type": ["string", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "query_parameters": { + "type": ["string", "null"] + }, + "referrer_url": { + "type": ["string", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + }, + "user_agent": { + "type": ["string", "null"] + }, + "webform_id": { + "type": ["integer", "null"] + }, + "webpage_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_open_email", + "stream": "activities_open_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 10, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "device"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "is_mobile_device"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "platform"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "user_agent"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "device": { + "type": ["string", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "is_mobile_device": { + "type": ["boolean", "null"] + }, + "platform": { + "type": ["string", "null"] + }, + "step_id": { + "type": ["integer", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + }, + "user_agent": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_click_email", + "stream": "activities_click_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 11, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "device"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "is_mobile_device"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "is_predictive"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "link"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "link_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "platform"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "user_agent"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "device": { + "type": ["string", "null"] + }, + "is_mobile_device": { + "type": ["boolean", "null"] + }, + "is_predictive": { + "type": ["boolean", "null"] + }, + "link": { + "type": ["string", "null"] + }, + "link_id": { + "type": ["string", "null"] + }, + "platform": { + "type": ["string", "null"] + }, + "step_id": { + "type": ["integer", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + }, + "user_agent": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_new_lead", + "stream": "activities_new_lead", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "created_date"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "form_name"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "lead_source"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "list_name"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "sfdc_type"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "source_type"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "api_method_name"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "modifying_user"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "request_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.activity-id": 12, + "table-key-properties": ["marketoGUID"] + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "created_date": { + "type": ["string", "null"], + "format": "date-time" + }, + "form_name": { + "type": ["string", "null"] + }, + "lead_source": { + "type": ["string", "null"] + }, + "list_name": { + "type": ["string", "null"] + }, + "sfdc_type": { + "type": ["string", "null"] + }, + "source_type": { + "type": ["string", "null"] + }, + "api_method_name": { + "type": ["string", "null"] + }, + "modifying_user": { + "type": ["string", "null"] + }, + "request_id": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_change_data_value", + "stream": "activities_change_data_value", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "attribute_name", + "marketo.activity-id": 13, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "new_value"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "old_value"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "api_method_name"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "modifying_user"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "request_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "new_value": { + "type": ["string", "null"] + }, + "old_value": { + "type": ["string", "null"] + }, + "reason": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + }, + "api_method_name": { + "type": ["string", "null"] + }, + "modifying_user": { + "type": ["string", "null"] + }, + "request_id": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_change_score", + "stream": "activities_change_score", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "score_name", + "marketo.activity-id": 22, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "change_value"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "new_value"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "old_value"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "change_value": { + "type": ["string", "null"] + }, + "new_value": { + "type": ["integer", "null"] + }, + "old_value": { + "type": ["integer", "null"] + }, + "reason": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_add_to_list", + "stream": "activities_add_to_list", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "list_id", + "marketo.activity-id": 24, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "source": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_remove_from_list", + "stream": "activities_remove_from_list", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "list_id", + "marketo.activity-id": 25, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "source": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_email_bounced_soft", + "stream": "activities_email_bounced_soft", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 27, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "category"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "details"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "email"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "subcategory"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "category": { + "type": ["string", "null"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "details": { + "type": ["string", "null"] + }, + "email": { + "type": ["string", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "step_id": { + "type": ["integer", "null"] + }, + "subcategory": { + "type": ["string", "null"] + }, + "test_variant": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_merge_leads", + "stream": "activities_merge_leads", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "lead_id", + "marketo.activity-id": 32, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "merge_ids"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "master_updated"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "merged_in_sales"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "merge_source"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "api_method_name"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "modifying_user"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "request_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "merge_ids": { + "type": ["array", "null"], + "items": { + "type": ["integer", "number", "string", "null"] + } + }, + "master_updated": { + "type": ["boolean", "null"] + }, + "merged_in_sales": { + "type": ["boolean", "null"] + }, + "merge_source": { + "type": ["string", "null"] + }, + "api_method_name": { + "type": ["string", "null"] + }, + "modifying_user": { + "type": ["string", "null"] + }, + "request_id": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_add_to_opportunity", + "stream": "activities_add_to_opportunity", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "oppty_id", + "marketo.activity-id": 34, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "is_primary"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "role"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "is_primary": { + "type": ["boolean", "null"] + }, + "role": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_remove_from_opportunity", + "stream": "activities_remove_from_opportunity", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "oppty_id", + "marketo.activity-id": 35, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "is_primary"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "role"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "is_primary": { + "type": ["boolean", "null"] + }, + "role": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_update_opportunity", + "stream": "activities_update_opportunity", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "oppty_id", + "marketo.activity-id": 36, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "attribute_name"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "data_value_changes"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "new_value"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "old_value"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "attribute_name": { + "type": ["string", "null"] + }, + "data_value_changes": { + "type": ["string", "null"] + }, + "new_value": { + "type": ["string", "null"] + }, + "old_value": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_delete_lead", + "stream": "activities_delete_lead", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "lead_id", + "marketo.activity-id": 37, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "remove_from_crm"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "api_method_name"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "modifying_user"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "request_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "remove_from_crm": { + "type": ["boolean", "null"] + }, + "api_method_name": { + "type": ["string", "null"] + }, + "modifying_user": { + "type": ["string", "null"] + }, + "request_id": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_send_alert", + "stream": "activities_send_alert", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 38, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "send_to_list"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "send_to_owner"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "send_to_smart_list"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "send_to_list": { + "type": ["string", "null"] + }, + "send_to_owner": { + "type": ["string", "null"] + }, + "send_to_smart_list": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_send_sales_email", + "stream": "activities_send_sales_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "artifact_id", + "marketo.activity-id": 39, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "sent_by"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "template_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "sent_by": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + }, + "template_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_open_sales_email", + "stream": "activities_open_sales_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "artifact_id", + "marketo.activity-id": 40, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "sent_by"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "template_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "sent_by": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + }, + "template_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_click_sales_email", + "stream": "activities_click_sales_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "artifact_id", + "marketo.activity-id": 41, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "is_predictive"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "link"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "sent_by"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "template_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "is_predictive": { + "type": ["boolean", "null"] + }, + "link": { + "type": ["string", "null"] + }, + "sent_by": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + }, + "template_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_receive_sales_email", + "stream": "activities_receive_sales_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "artifact_id", + "marketo.activity-id": 45, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "received_by"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "received_by": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_request_campaign", + "stream": "activities_request_campaign", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "campaign_id", + "marketo.activity-id": 47, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "source": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_sales_email_bounced", + "stream": "activities_sales_email_bounced", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "artifact_id", + "marketo.activity-id": 48, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "category"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "details"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "email"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "sent_by"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "subcategory"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "campaign_run_id": { + "type": ["integer", "null"] + }, + "category": { + "type": ["string", "null"] + }, + "details": { + "type": ["string", "null"] + }, + "email": { + "type": ["string", "null"] + }, + "has_predictive": { + "type": ["boolean", "null"] + }, + "sent_by": { + "type": ["string", "null"] + }, + "subcategory": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_change_lead_partition", + "stream": "activities_change_lead_partition", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "new_partition_id", + "marketo.activity-id": 100, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "old_partition_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "old_partition_id": { + "type": ["integer", "null"] + }, + "reason": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_change_revenue_stage", + "stream": "activities_change_revenue_stage", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "model_id", + "marketo.activity-id": 101, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "new_stage_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "old_stage_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "sla_expiration"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "new_stage"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "old_stage"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "new_stage_id": { + "type": ["integer", "null"] + }, + "old_stage_id": { + "type": ["integer", "null"] + }, + "reason": { + "type": ["string", "null"] + }, + "sla_expiration": { + "type": ["string", "null"], + "format": "date-time" + }, + "new_stage": { + "type": ["string", "null"] + }, + "old_stage": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_change_revenue_stage_manually", + "stream": "activities_change_revenue_stage_manually", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.activity-id": 102, + "table-key-properties": ["marketoGUID"] + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + } + } + } + }, + { + "tap_stream_id": "activities_change_status_in_progression", + "stream": "activities_change_status_in_progression", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "program_id", + "marketo.activity-id": 104, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "acquired_by"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "new_status_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "old_status_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "program_member_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "success"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "new_status"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "old_status"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "acquired_by": { + "type": ["boolean", "null"] + }, + "new_status_id": { + "type": ["integer", "null"] + }, + "old_status_id": { + "type": ["integer", "null"] + }, + "program_member_id": { + "type": ["integer", "null"] + }, + "reason": { + "type": ["string", "null"] + }, + "success": { + "type": ["boolean", "null"] + }, + "new_status": { + "type": ["string", "null"] + }, + "old_status": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_change_segment", + "stream": "activities_change_segment", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "segmentation_id", + "marketo.activity-id": 108, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "new_segment_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "new_segment_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_call_webhook", + "stream": "activities_call_webhook", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "webhook", + "marketo.activity-id": 110, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "error_type"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "response"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "error_type": { + "type": ["integer", "null"] + }, + "response": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_sent_forward_to_friend_email", + "stream": "activities_sent_forward_to_friend_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 111, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "lead_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "lead_id": { + "type": ["string", "null"] + }, + "step_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_received_forward_to_friend_email", + "stream": "activities_received_forward_to_friend_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 112, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "lead_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "choice_number": { + "type": ["integer", "null"] + }, + "lead_id": { + "type": ["string", "null"] + }, + "step_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_add_to_nurture", + "stream": "activities_add_to_nurture", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "program_id", + "marketo.activity-id": 113, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "track_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "track_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_change_nurture_track", + "stream": "activities_change_nurture_track", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "program_id", + "marketo.activity-id": 114, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "new_track_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "previous_track_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "new_track_id": { + "type": ["integer", "null"] + }, + "previous_track_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_change_nurture_cadence", + "stream": "activities_change_nurture_cadence", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "program_id", + "marketo.activity-id": 115, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "new_nurture_cadence"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "previous_nurture_cadence"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "new_nurture_cadence": { + "type": ["string", "null"] + }, + "previous_nurture_cadence": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_change_program_member_data", + "stream": "activities_change_program_member_data", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "program_id", + "marketo.activity-id": 123, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "attribute_display_name"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "attribute_name"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "new_value"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "old_value"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "attribute_display_name": { + "type": ["string", "null"] + }, + "attribute_name": { + "type": ["integer", "null"] + }, + "new_value": { + "type": ["string", "null"] + }, + "old_value": { + "type": ["string", "null"] + }, + "reason": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_push_lead_to_marketo", + "stream": "activities_push_lead_to_marketo", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "program_id", + "marketo.activity-id": 145, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "api_method_name"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "modifying_user"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "request_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "reason": { + "type": ["string", "null"] + }, + "source": { + "type": ["string", "null"] + }, + "api_method_name": { + "type": ["string", "null"] + }, + "modifying_user": { + "type": ["string", "null"] + }, + "request_id": { + "type": ["string", "null"] + } + } + } + }, + { + "tap_stream_id": "activities_share_content", + "stream": "activities_share_content", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "social_app_id", + "marketo.activity-id": 400, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "share_message"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "social_app_type_id"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "social_network"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "webpage_id"], + "metadata": { + "inclusion": "available" + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { + "type": ["null", "string"] + }, + "leadId": { + "type": ["null", "integer"] + }, + "activityDate": { + "type": ["null", "string"], + "format": "date-time" + }, + "activityTypeId": { + "type": ["null", "integer"] + }, + "primary_attribute_value": { + "type": ["null", "string"] + }, + "primary_attribute_name": { + "type": ["null", "string"] + }, + "primary_attribute_value_id": { + "type": ["null", "string"] + }, + "share_message": { + "type": ["string", "null"] + }, + "social_app_type_id": { + "type": ["integer", "null"] + }, + "social_network": { + "type": ["string", "null"] + }, + "webpage_id": { + "type": ["integer", "null"] + } + } + } + }, + { + "tap_stream_id": "campaigns", + "stream": "campaigns", + "key_properties": ["id"], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "active": { + "type": ["boolean", "null"] + }, + "description": { + "type": ["string", "null"] + }, + "name": { + "type": "string" + }, + "programId": { + "type": ["integer", "null"] + }, + "programName": { + "type": ["string", "null"] + }, + "type": { + "type": "string" + }, + "workspaceName": { + "type": ["string", "null"] + } + } + }, + "metadata": [ + { + "breadcrumb": ["properties", "id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "createdAt"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "updatedAt"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "active"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "description"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "name"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "programId"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "programName"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "type"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "workspaceName"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": [], + "metadata": { + "table-key-properties": ["id"] + } + } + ] + }, + { + "tap_stream_id": "lists", + "stream": "lists", + "key_properties": ["id"], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "description": { + "type": ["string", "null"] + }, + "programName": { + "type": ["string", "null"] + }, + "workspaceName": { + "type": ["string", "null"] + } + } + }, + "metadata": [ + { + "breadcrumb": ["properties", "id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "name"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "createdAt"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "updatedAt"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "description"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "programName"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "workspaceName"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": [], + "metadata": { + "table-key-properties": ["id"] + } + } + ] + }, + { + "tap_stream_id": "programs", + "stream": "programs", + "key_properties": ["id"], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "name": { + "type": "string" + }, + "description": { + "type": ["null", "string"] + }, + "url": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "channel": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "workspace": { + "type": ["null", "string"] + }, + "folder": { + "type": "object", + "properties": { + "type": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "integer"] + }, + "folderName": { + "type": ["null", "string"] + } + } + } + } + }, + "metadata": [ + { + "breadcrumb": ["properties", "id"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "createdAt"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "updatedAt"], + "metadata": { + "inclusion": "automatic" + } + }, + { + "breadcrumb": ["properties", "name"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "description"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "url"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "type"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "channel"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "status"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "workspace"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": ["properties", "folder"], + "metadata": { + "inclusion": "available" + } + }, + { + "breadcrumb": [], + "metadata": { + "table-key-properties": ["id"] + } + } + ] + } + ] +} diff --git a/airbyte-integrations/connectors/source-marketo-singer/singer_rendered_catalog.json b/airbyte-integrations/connectors/source-marketo-singer/singer_rendered_catalog.json new file mode 100644 index 0000000000000..7176dc2f8b5a6 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/singer_rendered_catalog.json @@ -0,0 +1,3952 @@ +{ + "streams": [ + { + "tap_stream_id": "leads", + "stream": "leads", + "key_properties": ["id"], + "metadata": [ + { + "breadcrumb": ["properties", "company"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "site"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "billingStreet"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "billingCity"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "billingState"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "billingCountry"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "billingPostalCode"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "website"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "mainPhone"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "annualRevenue"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "numberOfEmployees"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "industry"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "sicCode"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "mktoCompanyNotes"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "externalCompanyId"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "mktoName"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "personType"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "mktoIsPartner"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "isLead"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "mktoIsCustomer"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "isAnonymous"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "salutation"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "firstName"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "middleName"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "lastName"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "email"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "phone"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "mobilePhone"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "fax"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "title"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "contactCompany"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "dateOfBirth"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "address"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "city"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "state"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "country"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "postalCode"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "personTimeZone"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "originalSourceType"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "originalSourceInfo"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "registrationSourceType"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "registrationSourceInfo"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "originalSearchEngine"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "originalSearchPhrase"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "originalReferrer"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "emailInvalid"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "emailInvalidCause"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "unsubscribed"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "unsubscribedReason"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "doNotCall"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "mktoDoNotCallCause"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "doNotCallReason"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "marketingSuspended"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "marketingSuspendedCause"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "blackListed"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "blackListedCause"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "mktoPersonNotes"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "anonymousIP"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "inferredCompany"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "inferredCountry"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "inferredCity"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "inferredStateRegion"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "inferredPostalCode"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "inferredMetropolitanArea"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "inferredPhoneAreaCode"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "emailSuspended"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "emailSuspendedCause"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "emailSuspendedAt"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "department"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "createdAt"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "updatedAt"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "cookies"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "externalSalesPersonId"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "leadPerson"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "leadRole"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "leadSource"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "leadStatus"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "leadScore"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "urgency"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "priority"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "relativeScore"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "relativeUrgency"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "rating"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "personPrimaryLeadInterest"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "leadPartitionId"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "leadRevenueCycleModelId"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "leadRevenueStageId"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "acquisitionProgramId"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "mktoAcquisitionDate"], + "metadata": { "inclusion": "available" } + }, + { "breadcrumb": [], "metadata": { "table-key-properties": ["id"] } } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "company": { "type": ["string", "null"] }, + "site": { "type": ["string", "null"] }, + "billingStreet": { "type": ["string", "null"] }, + "billingCity": { "type": ["string", "null"] }, + "billingState": { "type": ["string", "null"] }, + "billingCountry": { "type": ["string", "null"] }, + "billingPostalCode": { "type": ["string", "null"] }, + "website": { "type": ["string", "null"] }, + "mainPhone": { "type": ["string", "null"] }, + "annualRevenue": { "type": ["number", "null"] }, + "numberOfEmployees": { "type": ["integer", "null"] }, + "industry": { "type": ["string", "null"] }, + "sicCode": { "type": ["string", "null"] }, + "mktoCompanyNotes": { "type": ["string", "null"] }, + "externalCompanyId": { "type": ["string", "null"] }, + "id": { "type": "integer" }, + "mktoName": { "type": ["string", "null"] }, + "personType": { "type": ["string", "null"] }, + "mktoIsPartner": { "type": ["boolean", "null"] }, + "isLead": { "type": ["boolean", "null"] }, + "mktoIsCustomer": { "type": ["boolean", "null"] }, + "isAnonymous": { "type": ["boolean", "null"] }, + "salutation": { "type": ["string", "null"] }, + "firstName": { "type": ["string", "null"] }, + "middleName": { "type": ["string", "null"] }, + "lastName": { "type": ["string", "null"] }, + "email": { "type": ["string", "null"] }, + "phone": { "type": ["string", "null"] }, + "mobilePhone": { "type": ["string", "null"] }, + "fax": { "type": ["string", "null"] }, + "title": { "type": ["string", "null"] }, + "contactCompany": { "type": ["string", "null"] }, + "dateOfBirth": { "type": ["string", "null"], "format": "date-time" }, + "address": { "type": ["string", "null"] }, + "city": { "type": ["string", "null"] }, + "state": { "type": ["string", "null"] }, + "country": { "type": ["string", "null"] }, + "postalCode": { "type": ["string", "null"] }, + "personTimeZone": { "type": ["string", "null"] }, + "originalSourceType": { "type": ["string", "null"] }, + "originalSourceInfo": { "type": ["string", "null"] }, + "registrationSourceType": { "type": ["string", "null"] }, + "registrationSourceInfo": { "type": ["string", "null"] }, + "originalSearchEngine": { "type": ["string", "null"] }, + "originalSearchPhrase": { "type": ["string", "null"] }, + "originalReferrer": { "type": ["string", "null"] }, + "emailInvalid": { "type": ["boolean", "null"] }, + "emailInvalidCause": { "type": ["string", "null"] }, + "unsubscribed": { "type": ["boolean", "null"] }, + "unsubscribedReason": { "type": ["string", "null"] }, + "doNotCall": { "type": ["boolean", "null"] }, + "mktoDoNotCallCause": { "type": ["string", "null"] }, + "doNotCallReason": { "type": ["string", "null"] }, + "marketingSuspended": { "type": ["boolean", "null"] }, + "marketingSuspendedCause": { "type": ["string", "null"] }, + "blackListed": { "type": ["boolean", "null"] }, + "blackListedCause": { "type": ["string", "null"] }, + "mktoPersonNotes": { "type": ["string", "null"] }, + "anonymousIP": { "type": ["string", "null"] }, + "inferredCompany": { "type": ["string", "null"] }, + "inferredCountry": { "type": ["string", "null"] }, + "inferredCity": { "type": ["string", "null"] }, + "inferredStateRegion": { "type": ["string", "null"] }, + "inferredPostalCode": { "type": ["string", "null"] }, + "inferredMetropolitanArea": { "type": ["string", "null"] }, + "inferredPhoneAreaCode": { "type": ["string", "null"] }, + "emailSuspended": { "type": ["boolean", "null"] }, + "emailSuspendedCause": { "type": ["string", "null"] }, + "emailSuspendedAt": { + "type": ["string", "null"], + "format": "date-time" + }, + "department": { "type": ["string", "null"] }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" }, + "cookies": { "type": ["string", "null"] }, + "externalSalesPersonId": { "type": ["string", "null"] }, + "leadPerson": { "type": ["string", "null"] }, + "leadRole": { "type": ["string", "null"] }, + "leadSource": { "type": ["string", "null"] }, + "leadStatus": { "type": ["string", "null"] }, + "leadScore": { "type": ["integer", "null"] }, + "urgency": { "type": ["number", "null"] }, + "priority": { "type": ["integer", "null"] }, + "relativeScore": { "type": ["integer", "null"] }, + "relativeUrgency": { "type": ["integer", "null"] }, + "rating": { "type": ["string", "null"] }, + "personPrimaryLeadInterest": { "type": ["string", "null"] }, + "leadPartitionId": { "type": ["string", "null"] }, + "leadRevenueCycleModelId": { "type": ["string", "null"] }, + "leadRevenueStageId": { "type": ["string", "null"] }, + "acquisitionProgramId": { "type": ["string", "null"] }, + "mktoAcquisitionDate": { + "type": ["string", "null"], + "format": "date-time" + } + } + } + }, + { + "tap_stream_id": "activity_types", + "stream": "activity_types", + "key_properties": ["id"], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "description": { "type": ["string", "null"] }, + "primaryAttribute": { + "type": ["object", "null"], + "properties": { + "name": { "type": "string" }, + "dataType": { "type": "string" } + } + }, + "attributes": { + "type": ["array", "null"], + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "dataType": { "type": "string" } + } + } + } + }, + "selected": true + }, + "metadata": [ + { + "breadcrumb": ["properties", "id"], + "metadata": { "inclusion": "automatic", "selected": true } + }, + { + "breadcrumb": ["properties", "name"], + "metadata": { "inclusion": "automatic", "selected": true } + }, + { + "breadcrumb": ["properties", "description"], + "metadata": { "inclusion": "available", "selected": true } + }, + { + "breadcrumb": ["properties", "primaryAttribute"], + "metadata": { "inclusion": "available", "selected": true } + }, + { + "breadcrumb": ["properties", "attributes"], + "metadata": { "inclusion": "unsupported", "selected": true } + }, + { + "breadcrumb": [], + "metadata": { + "inclusion": "automatic", + "table-key-properties": ["id"], + "selected": true, + "forced-replication-method": "FULL_TABLE", + "replication-method": "FULL_TABLE" + } + } + ] + }, + { + "tap_stream_id": "activities_visit_webpage", + "stream": "activities_visit_webpage", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "webpage_id", + "marketo.activity-id": 1, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "client_ip_address"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "query_parameters"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "referrer_url"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "search_engine"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "search_query"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "user_agent"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "webpage_url"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "client_ip_address": { "type": ["string", "null"] }, + "query_parameters": { "type": ["string", "null"] }, + "referrer_url": { "type": ["string", "null"] }, + "search_engine": { "type": ["string", "null"] }, + "search_query": { "type": ["string", "null"] }, + "user_agent": { "type": ["string", "null"] }, + "webpage_url": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_fill_out_form", + "stream": "activities_fill_out_form", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "webform_id", + "marketo.activity-id": 2, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "client_ip_address"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "form_fields"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "query_parameters"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "referrer_url"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "user_agent"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "webpage_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "client_ip_address": { "type": ["string", "null"] }, + "form_fields": { "type": ["string", "null"] }, + "query_parameters": { "type": ["string", "null"] }, + "referrer_url": { "type": ["string", "null"] }, + "user_agent": { "type": ["string", "null"] }, + "webpage_id": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_click_link", + "stream": "activities_click_link", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "link_id", + "marketo.activity-id": 3, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "client_ip_address"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "query_parameters"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "referrer_url"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "user_agent"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "webpage_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "client_ip_address": { "type": ["string", "null"] }, + "query_parameters": { "type": ["string", "null"] }, + "referrer_url": { "type": ["string", "null"] }, + "user_agent": { "type": ["string", "null"] }, + "webpage_id": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_send_email", + "stream": "activities_send_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 6, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "campaign_run_id": { "type": ["integer", "null"] }, + "choice_number": { "type": ["integer", "null"] }, + "has_predictive": { "type": ["boolean", "null"] }, + "step_id": { "type": ["integer", "null"] }, + "test_variant": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_email_delivered", + "stream": "activities_email_delivered", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 7, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "campaign_run_id": { "type": ["integer", "null"] }, + "choice_number": { "type": ["integer", "null"] }, + "has_predictive": { "type": ["boolean", "null"] }, + "step_id": { "type": ["integer", "null"] }, + "test_variant": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_email_bounced", + "stream": "activities_email_bounced", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 8, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "category"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "details"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "email"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "subcategory"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "campaign_run_id": { "type": ["integer", "null"] }, + "category": { "type": ["string", "null"] }, + "choice_number": { "type": ["integer", "null"] }, + "details": { "type": ["string", "null"] }, + "email": { "type": ["string", "null"] }, + "has_predictive": { "type": ["boolean", "null"] }, + "step_id": { "type": ["integer", "null"] }, + "subcategory": { "type": ["string", "null"] }, + "test_variant": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_unsubscribe_email", + "stream": "activities_unsubscribe_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 9, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "client_ip_address"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "form_fields"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "query_parameters"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "referrer_url"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "user_agent"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "webform_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "webpage_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "campaign_run_id": { "type": ["string", "null"] }, + "client_ip_address": { "type": ["string", "null"] }, + "form_fields": { "type": ["string", "null"] }, + "has_predictive": { "type": ["boolean", "null"] }, + "query_parameters": { "type": ["string", "null"] }, + "referrer_url": { "type": ["string", "null"] }, + "test_variant": { "type": ["integer", "null"] }, + "user_agent": { "type": ["string", "null"] }, + "webform_id": { "type": ["integer", "null"] }, + "webpage_id": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_open_email", + "stream": "activities_open_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 10, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "device"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "is_mobile_device"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "platform"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "user_agent"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "campaign_run_id": { "type": ["integer", "null"] }, + "choice_number": { "type": ["integer", "null"] }, + "device": { "type": ["string", "null"] }, + "has_predictive": { "type": ["boolean", "null"] }, + "is_mobile_device": { "type": ["boolean", "null"] }, + "platform": { "type": ["string", "null"] }, + "step_id": { "type": ["integer", "null"] }, + "test_variant": { "type": ["integer", "null"] }, + "user_agent": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_click_email", + "stream": "activities_click_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 11, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "device"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "is_mobile_device"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "is_predictive"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "link"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "link_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "platform"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "user_agent"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "campaign_run_id": { "type": ["integer", "null"] }, + "choice_number": { "type": ["integer", "null"] }, + "device": { "type": ["string", "null"] }, + "is_mobile_device": { "type": ["boolean", "null"] }, + "is_predictive": { "type": ["boolean", "null"] }, + "link": { "type": ["string", "null"] }, + "link_id": { "type": ["string", "null"] }, + "platform": { "type": ["string", "null"] }, + "step_id": { "type": ["integer", "null"] }, + "test_variant": { "type": ["integer", "null"] }, + "user_agent": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_new_lead", + "stream": "activities_new_lead", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "created_date"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "form_name"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "lead_source"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "list_name"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "sfdc_type"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "source_type"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "api_method_name"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "modifying_user"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "request_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.activity-id": 12, + "table-key-properties": ["marketoGUID"] + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "created_date": { "type": ["string", "null"], "format": "date-time" }, + "form_name": { "type": ["string", "null"] }, + "lead_source": { "type": ["string", "null"] }, + "list_name": { "type": ["string", "null"] }, + "sfdc_type": { "type": ["string", "null"] }, + "source_type": { "type": ["string", "null"] }, + "api_method_name": { "type": ["string", "null"] }, + "modifying_user": { "type": ["string", "null"] }, + "request_id": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_change_data_value", + "stream": "activities_change_data_value", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "attribute_name", + "marketo.activity-id": 13, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "new_value"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "old_value"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "api_method_name"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "modifying_user"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "request_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "new_value": { "type": ["string", "null"] }, + "old_value": { "type": ["string", "null"] }, + "reason": { "type": ["string", "null"] }, + "source": { "type": ["string", "null"] }, + "api_method_name": { "type": ["string", "null"] }, + "modifying_user": { "type": ["string", "null"] }, + "request_id": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_change_score", + "stream": "activities_change_score", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "score_name", + "marketo.activity-id": 22, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "change_value"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "new_value"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "old_value"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "change_value": { "type": ["string", "null"] }, + "new_value": { "type": ["integer", "null"] }, + "old_value": { "type": ["integer", "null"] }, + "reason": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_add_to_list", + "stream": "activities_add_to_list", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "list_id", + "marketo.activity-id": 24, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "source": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_remove_from_list", + "stream": "activities_remove_from_list", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "list_id", + "marketo.activity-id": 25, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "source": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_email_bounced_soft", + "stream": "activities_email_bounced_soft", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 27, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "category"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "details"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "email"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "subcategory"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "test_variant"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "campaign_run_id": { "type": ["integer", "null"] }, + "category": { "type": ["string", "null"] }, + "choice_number": { "type": ["integer", "null"] }, + "details": { "type": ["string", "null"] }, + "email": { "type": ["string", "null"] }, + "has_predictive": { "type": ["boolean", "null"] }, + "step_id": { "type": ["integer", "null"] }, + "subcategory": { "type": ["string", "null"] }, + "test_variant": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_merge_leads", + "stream": "activities_merge_leads", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "lead_id", + "marketo.activity-id": 32, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "merge_ids"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "master_updated"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "merged_in_sales"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "merge_source"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "api_method_name"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "modifying_user"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "request_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "merge_ids": { + "type": ["array", "null"], + "items": { "type": ["integer", "number", "string", "null"] } + }, + "master_updated": { "type": ["boolean", "null"] }, + "merged_in_sales": { "type": ["boolean", "null"] }, + "merge_source": { "type": ["string", "null"] }, + "api_method_name": { "type": ["string", "null"] }, + "modifying_user": { "type": ["string", "null"] }, + "request_id": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_add_to_opportunity", + "stream": "activities_add_to_opportunity", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "oppty_id", + "marketo.activity-id": 34, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "is_primary"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "role"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "is_primary": { "type": ["boolean", "null"] }, + "role": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_remove_from_opportunity", + "stream": "activities_remove_from_opportunity", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "oppty_id", + "marketo.activity-id": 35, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "is_primary"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "role"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "is_primary": { "type": ["boolean", "null"] }, + "role": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_update_opportunity", + "stream": "activities_update_opportunity", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "oppty_id", + "marketo.activity-id": 36, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "attribute_name"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "data_value_changes"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "new_value"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "old_value"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "attribute_name": { "type": ["string", "null"] }, + "data_value_changes": { "type": ["string", "null"] }, + "new_value": { "type": ["string", "null"] }, + "old_value": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_delete_lead", + "stream": "activities_delete_lead", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "lead_id", + "marketo.activity-id": 37, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "remove_from_crm"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "api_method_name"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "modifying_user"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "request_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "remove_from_crm": { "type": ["boolean", "null"] }, + "api_method_name": { "type": ["string", "null"] }, + "modifying_user": { "type": ["string", "null"] }, + "request_id": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_send_alert", + "stream": "activities_send_alert", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 38, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "send_to_list"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "send_to_owner"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "send_to_smart_list"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "send_to_list": { "type": ["string", "null"] }, + "send_to_owner": { "type": ["string", "null"] }, + "send_to_smart_list": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_send_sales_email", + "stream": "activities_send_sales_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "artifact_id", + "marketo.activity-id": 39, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "sent_by"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "template_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "campaign_run_id": { "type": ["integer", "null"] }, + "has_predictive": { "type": ["boolean", "null"] }, + "sent_by": { "type": ["string", "null"] }, + "source": { "type": ["string", "null"] }, + "template_id": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_open_sales_email", + "stream": "activities_open_sales_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "artifact_id", + "marketo.activity-id": 40, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "sent_by"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "template_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "campaign_run_id": { "type": ["integer", "null"] }, + "has_predictive": { "type": ["boolean", "null"] }, + "sent_by": { "type": ["string", "null"] }, + "source": { "type": ["string", "null"] }, + "template_id": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_click_sales_email", + "stream": "activities_click_sales_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "artifact_id", + "marketo.activity-id": 41, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "is_predictive"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "link"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "sent_by"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "template_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "campaign_run_id": { "type": ["integer", "null"] }, + "is_predictive": { "type": ["boolean", "null"] }, + "link": { "type": ["string", "null"] }, + "sent_by": { "type": ["string", "null"] }, + "source": { "type": ["string", "null"] }, + "template_id": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_receive_sales_email", + "stream": "activities_receive_sales_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "artifact_id", + "marketo.activity-id": 45, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "received_by"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "received_by": { "type": ["string", "null"] }, + "source": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_request_campaign", + "stream": "activities_request_campaign", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "campaign_id", + "marketo.activity-id": 47, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "source": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_sales_email_bounced", + "stream": "activities_sales_email_bounced", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "artifact_id", + "marketo.activity-id": 48, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "campaign_run_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "category"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "details"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "email"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "has_predictive"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "sent_by"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "subcategory"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "campaign_run_id": { "type": ["integer", "null"] }, + "category": { "type": ["string", "null"] }, + "details": { "type": ["string", "null"] }, + "email": { "type": ["string", "null"] }, + "has_predictive": { "type": ["boolean", "null"] }, + "sent_by": { "type": ["string", "null"] }, + "subcategory": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_change_lead_partition", + "stream": "activities_change_lead_partition", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "new_partition_id", + "marketo.activity-id": 100, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "old_partition_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "old_partition_id": { "type": ["integer", "null"] }, + "reason": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_change_revenue_stage", + "stream": "activities_change_revenue_stage", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "model_id", + "marketo.activity-id": 101, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "new_stage_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "old_stage_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "sla_expiration"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "new_stage"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "old_stage"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "new_stage_id": { "type": ["integer", "null"] }, + "old_stage_id": { "type": ["integer", "null"] }, + "reason": { "type": ["string", "null"] }, + "sla_expiration": { + "type": ["string", "null"], + "format": "date-time" + }, + "new_stage": { "type": ["string", "null"] }, + "old_stage": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_change_revenue_stage_manually", + "stream": "activities_change_revenue_stage_manually", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.activity-id": 102, + "table-key-properties": ["marketoGUID"] + } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] } + } + } + }, + { + "tap_stream_id": "activities_change_status_in_progression", + "stream": "activities_change_status_in_progression", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "program_id", + "marketo.activity-id": 104, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "acquired_by"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "new_status_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "old_status_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "program_member_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "success"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "new_status"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "old_status"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "acquired_by": { "type": ["boolean", "null"] }, + "new_status_id": { "type": ["integer", "null"] }, + "old_status_id": { "type": ["integer", "null"] }, + "program_member_id": { "type": ["integer", "null"] }, + "reason": { "type": ["string", "null"] }, + "success": { "type": ["boolean", "null"] }, + "new_status": { "type": ["string", "null"] }, + "old_status": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_change_segment", + "stream": "activities_change_segment", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "segmentation_id", + "marketo.activity-id": 108, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "new_segment_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "new_segment_id": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_call_webhook", + "stream": "activities_call_webhook", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "webhook", + "marketo.activity-id": 110, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "error_type"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "response"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "error_type": { "type": ["integer", "null"] }, + "response": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_sent_forward_to_friend_email", + "stream": "activities_sent_forward_to_friend_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 111, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "lead_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "choice_number": { "type": ["integer", "null"] }, + "lead_id": { "type": ["string", "null"] }, + "step_id": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_received_forward_to_friend_email", + "stream": "activities_received_forward_to_friend_email", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "mailing_id", + "marketo.activity-id": 112, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "choice_number"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "lead_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "step_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "choice_number": { "type": ["integer", "null"] }, + "lead_id": { "type": ["string", "null"] }, + "step_id": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_add_to_nurture", + "stream": "activities_add_to_nurture", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "program_id", + "marketo.activity-id": 113, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "track_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "track_id": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_change_nurture_track", + "stream": "activities_change_nurture_track", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "program_id", + "marketo.activity-id": 114, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "new_track_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "previous_track_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "new_track_id": { "type": ["integer", "null"] }, + "previous_track_id": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "activities_change_nurture_cadence", + "stream": "activities_change_nurture_cadence", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "program_id", + "marketo.activity-id": 115, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "new_nurture_cadence"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "previous_nurture_cadence"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "new_nurture_cadence": { "type": ["string", "null"] }, + "previous_nurture_cadence": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_change_program_member_data", + "stream": "activities_change_program_member_data", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "program_id", + "marketo.activity-id": 123, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "attribute_display_name"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "attribute_name"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "new_value"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "old_value"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "attribute_display_name": { "type": ["string", "null"] }, + "attribute_name": { "type": ["integer", "null"] }, + "new_value": { "type": ["string", "null"] }, + "old_value": { "type": ["string", "null"] }, + "reason": { "type": ["string", "null"] }, + "source": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_push_lead_to_marketo", + "stream": "activities_push_lead_to_marketo", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "program_id", + "marketo.activity-id": 145, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "reason"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "source"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "api_method_name"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "modifying_user"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "request_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "reason": { "type": ["string", "null"] }, + "source": { "type": ["string", "null"] }, + "api_method_name": { "type": ["string", "null"] }, + "modifying_user": { "type": ["string", "null"] }, + "request_id": { "type": ["string", "null"] } + } + } + }, + { + "tap_stream_id": "activities_share_content", + "stream": "activities_share_content", + "key_properties": ["marketoGUID"], + "metadata": [ + { + "breadcrumb": ["properties", "marketoGUID"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "leadId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityDate"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "activityTypeId"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "primary_attribute_value_id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": [], + "metadata": { + "marketo.primary-attribute-name": "social_app_id", + "marketo.activity-id": 400, + "table-key-properties": ["marketoGUID"] + } + }, + { + "breadcrumb": ["properties", "share_message"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "social_app_type_id"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "social_network"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "webpage_id"], + "metadata": { "inclusion": "available" } + } + ], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "marketoGUID": { "type": ["null", "string"] }, + "leadId": { "type": ["null", "integer"] }, + "activityDate": { "type": ["null", "string"], "format": "date-time" }, + "activityTypeId": { "type": ["null", "integer"] }, + "primary_attribute_value": { "type": ["null", "string"] }, + "primary_attribute_name": { "type": ["null", "string"] }, + "primary_attribute_value_id": { "type": ["null", "string"] }, + "share_message": { "type": ["string", "null"] }, + "social_app_type_id": { "type": ["integer", "null"] }, + "social_network": { "type": ["string", "null"] }, + "webpage_id": { "type": ["integer", "null"] } + } + } + }, + { + "tap_stream_id": "campaigns", + "stream": "campaigns", + "key_properties": ["id"], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { "type": "integer" }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" }, + "active": { "type": ["boolean", "null"] }, + "description": { "type": ["string", "null"] }, + "name": { "type": "string" }, + "programId": { "type": ["integer", "null"] }, + "programName": { "type": ["string", "null"] }, + "type": { "type": "string" }, + "workspaceName": { "type": ["string", "null"] } + } + }, + "metadata": [ + { + "breadcrumb": ["properties", "id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "createdAt"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "updatedAt"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "active"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "description"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "name"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "programId"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "programName"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "type"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "workspaceName"], + "metadata": { "inclusion": "available" } + }, + { "breadcrumb": [], "metadata": { "table-key-properties": ["id"] } } + ] + }, + { + "tap_stream_id": "lists", + "stream": "lists", + "key_properties": ["id"], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" }, + "description": { "type": ["string", "null"] }, + "programName": { "type": ["string", "null"] }, + "workspaceName": { "type": ["string", "null"] } + } + }, + "metadata": [ + { + "breadcrumb": ["properties", "id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "name"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "createdAt"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "updatedAt"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "description"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "programName"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "workspaceName"], + "metadata": { "inclusion": "available" } + }, + { "breadcrumb": [], "metadata": { "table-key-properties": ["id"] } } + ] + }, + { + "tap_stream_id": "programs", + "stream": "programs", + "key_properties": ["id"], + "schema": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { "type": "integer" }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" }, + "name": { "type": "string" }, + "description": { "type": ["null", "string"] }, + "url": { "type": ["null", "string"] }, + "type": { "type": ["null", "string"] }, + "channel": { "type": ["null", "string"] }, + "status": { "type": ["null", "string"] }, + "workspace": { "type": ["null", "string"] }, + "folder": { + "type": "object", + "properties": { + "type": { "type": ["null", "string"] }, + "value": { "type": ["null", "integer"] }, + "folderName": { "type": ["null", "string"] } + } + } + } + }, + "metadata": [ + { + "breadcrumb": ["properties", "id"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "createdAt"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "updatedAt"], + "metadata": { "inclusion": "automatic" } + }, + { + "breadcrumb": ["properties", "name"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "description"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "url"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "type"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "channel"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "status"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "workspace"], + "metadata": { "inclusion": "available" } + }, + { + "breadcrumb": ["properties", "folder"], + "metadata": { "inclusion": "available" } + }, + { "breadcrumb": [], "metadata": { "table-key-properties": ["id"] } } + ] + } + ] +} diff --git a/airbyte-integrations/connectors/source-marketo-singer/source_marketo_singer/__init__.py b/airbyte-integrations/connectors/source-marketo-singer/source_marketo_singer/__init__.py new file mode 100644 index 0000000000000..a8633de56cde6 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/source_marketo_singer/__init__.py @@ -0,0 +1,27 @@ +""" +MIT License + +Copyright (c) 2020 Airbyte + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from .source import SourceMarketoSinger + +__all__ = ["SourceMarketoSinger"] diff --git a/airbyte-integrations/connectors/source-marketo-singer/source_marketo_singer/source.py b/airbyte-integrations/connectors/source-marketo-singer/source_marketo_singer/source.py new file mode 100644 index 0000000000000..c919d4d94fc59 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/source_marketo_singer/source.py @@ -0,0 +1,52 @@ +""" +MIT License + +Copyright (c) 2020 Airbyte + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from airbyte_protocol import AirbyteConnectionStatus, Status +from base_python import AirbyteLogger, ConfigContainer +from base_singer import SingerSource + +TAP_CMD = "tap-marketo" + + +class SourceMarketoSinger(SingerSource): + def __init__(self): + super().__init__() + + def check(self, logger: AirbyteLogger, config_container: ConfigContainer) -> AirbyteConnectionStatus: + try: + self.discover(logger, config_container) + return AirbyteConnectionStatus(status=Status.SUCCEEDED) + except Exception as e: + logger.error("Exception while connecting to the Marketo API") + logger.error(str(e)) + return AirbyteConnectionStatus( + status=Status.FAILED, message="Unable to connect to the Marketo API with the provided credentials. " + ) + + def discover_cmd(self, logger: AirbyteLogger, config_path: str) -> str: + return f"{TAP_CMD} -c {config_path} --discover" + + def read_cmd(self, logger: AirbyteLogger, config_path: str, catalog_path: str, state_path: str = None) -> str: + state_flag = f"--state {state_path}" if state_path else "" + return f"{TAP_CMD} -c {config_path} -p {catalog_path} {state_flag}" diff --git a/airbyte-integrations/connectors/source-marketo-singer/source_marketo_singer/spec.json b/airbyte-integrations/connectors/source-marketo-singer/source_marketo_singer/spec.json new file mode 100644 index 0000000000000..d19648aff692e --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/source_marketo_singer/spec.json @@ -0,0 +1,40 @@ +{ + "documentationUrl": "https://docs.airbyte.io/integrations/sources/marketo", + "connectionSpecification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Source Marketo Singer Spec", + "type": "object", + "required": [ + "endpoint", + "identity", + "client_id", + "client_secret", + "start_date" + ], + "additionalProperties": false, + "properties": { + "endpoint_url": { + "type": "string", + "description": "Your Marketo Endpoint URL. See the docs for info on how to obtain this." + }, + "identity_url": { + "type": "string", + "description": "Your Marketo Identity URL. See the docs for info on how to obtain this." + }, + "client_id": { + "type": "string", + "description": "Your Marketo client_id. See the docs for info on how to obtain this." + }, + "client_secret": { + "type": "string", + "description": "Your Marketo client secret. See the docs for info on how to obtain this." + }, + "start_date": { + "type": "string", + "description": "Data generated in Marketo after this date will be replicated. This date must be specified in the format YYYY-MM-DDT00:00:00Z.", + "examples": ["2020-09-25T00:00:00Z"], + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" + } + } + } +} diff --git a/airbyte-integrations/connectors/source-marketo-singer/unit_tests/unit_test.py b/airbyte-integrations/connectors/source-marketo-singer/unit_tests/unit_test.py new file mode 100644 index 0000000000000..371f4e4b1ee00 --- /dev/null +++ b/airbyte-integrations/connectors/source-marketo-singer/unit_tests/unit_test.py @@ -0,0 +1,44 @@ +""" +MIT License + +Copyright (c) 2020 Airbyte + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from base_python import AirbyteLogger +from source_marketo_singer import SourceMarketoSinger + +logger = AirbyteLogger() +source = SourceMarketoSinger() +catalog = "/tmp/catalog.json" +config = "/tmp/config.json" +state = "/tmp/state.json" + + +def test_discover_cmd(): + assert f"tap-marketo -c {config} --discover" == source.discover_cmd(logger, config).strip() + + +def test_read_cmd_no_state(): + assert f"tap-marketo -c {config} -p {catalog}" == source.read_cmd(logger, config, catalog).strip() + + +def test_read_cmd_with_state(): + assert f"tap-marketo -c {config} -p {catalog} --state {state}" == source.read_cmd(logger, config, catalog, state).strip() diff --git a/docs/.gitbook/assets/screen-shot-2020-11-03-at-9.25.21-pm (1).png b/docs/.gitbook/assets/screen-shot-2020-11-03-at-9.25.21-pm (1).png new file mode 100644 index 0000000000000000000000000000000000000000..54f0734338af3c4aed317e0e6b2ecbbd3b8c712a GIT binary patch literal 260195 zcma%j1zgkX+c%6x2}MCb1Vu$qx@6QiNS6wNv=XDcyA%{jk!FCTgr&s9C6qh=^!Zlpj1MBBG=uA|j)w zBqu!i?D9Q}h=}-&jlBFr6?u7%hptXw8+%J4qC4>~<6&w5mn)rFYM+(glfV8zo*N!{ zzNn-2MX0lYXbp>t|WRMzr zt?T65H=is&7@Al*qUD~|7EzVN6DsIEA2D;oYh8CUDn63&+qXQ7W0HT*p+8hUSGYqI zGw#RV+mvEp%vzS299wQ?*%j!pS`R}uTr`s-x-VX78h1G{C4kdF$Fj5Vi{s1i8*LUD zBF8ZBU1U~qk)y65F+vsHiuKv^)n(z1|jJ8`SdjwPdBZy zREgewht31Jh*|?VgA{S;0qP;YQseCP52}tB6%B5L_zqxNcjIS z8}Mf>C1p0rpJg)o9}VTS};=oq_!PUS3{&Uc!7%u2%el5)u;p z0z&*kLcD|)yly^@?$5n>9o;y84)W(X4=mlxU2UA*ZJZoAevJFv%*n%D`o@hPFZ$2F zpLAM!+x+z=N4G!NA~4ARqlRCQPk{fwb#u1?|DxNEnxEZ%=j-R|q<#zreBx&5D(~dr zVCm>C^Ota?et*+HD*vMMXUB&&-j?=HAJ`Cjx)FGi5fTs;`=i_ctopBA_5bQBC?xTZ zu79igTh|{<0JU6g2$VkmAw?NMDgOWU?2q2sI;CpuccVIUeCh$GhAw{+vI(*Vt8s@7vqcwN_`p=IZKZSo3UWx>H;C z!xrxy<@X%HL;%Xae&un7oEL}LI=`u8?J8(v~^Rv>x6 zt9bC>$Utm|SWuN5eOa(>m-J`tJ}>>oWqSGpk;geCfZ5)d#8KjJO&f}HX&Ns-y!^m> z+j{Vc0%o;s>&ot{-)REbhrF0e=11g0z!@{udl^ckBXOWk5qDlA>c2Df7kmGsbaa6O z)J}K47AOp5_EUy*&W-J@9?Nqg#5#ip^@NGn&Wb@fQM|@YHxbA^$L6P%IV0u9l0`a2 zZ@bixr6#QgV)j!U)mDQ^bIllUG$XQcPv#X;)GhrB#$<@Z;%l5-dAU!mMSt3pBwW?K z2|@k%`yteqd6d1q8@AxJcU@>InErp0BhQC8ix{A1?D3?Xsckb>cJVy@`b3#w)WGql zm*=?dfEEhU4SG0ZrEWC3u0*8j#HNS^? zrqQvA-9sS9+9Q%p`~?-c+T840WoTXvv5Q`n@_t40%DL{uZ)>78eRj09d9dG+pC)vO z7S|AIUI49x=4@+z?;6RM?K+kj!d0(*<#W=6@N~Rks_z%Io0!$B?r!7r%ADQpSj+0V zKhWs5W-(LeuxizO`u#IzcPUo3W9r(bmPruKLm&3MRZ8;v^h4Pv19bntxx*J9JV->m zW^}kU&3ST&a?8Z}&sr$%oZJNpA($JX4wrciblhLGc=~oFdbHbUg^^$}XtdO2wmF!E zjZR(YYynBDTo`$s2Aj0BWzVIJ*?hGOPK;Kj6tfdob4*1n^vI4&DduV>a(oZrK+sh; zV4=j?x&56uqT*VU@yu8hF{_v&8!V-Agf^hdet-SZ?$Hu%QR=KPYnz|fux|hA04o13 zD+~YH!4OAyj>4Ox{-gz)FN447EOdVMEV*X2UhzKARuMX}Wak|03APgpQ!}2G(HF83 zThskyVf^8aM!Hyh;$6L-@83T6p9G!d%V@;J{@c>a`w&d~q5thIwJ7sU3Afvwd;6z1 z?dnQhOz_jfRwM%x+j9?B@1?H%_J!>At5@F07n@Q5WIn!5nebYXLu zpNp!&h)W08b6od9VKg<;Xmg}pksPbZQG@eGKetF(Q&?a?DW)Fr-LV-ZwlTdY$1LzHSZe1> z!fQN{*3P-b;J+Pv|7(i_(UXdB9z`(=o9(Y<$OOPWzvIzCX9+Mo_AaH83Pi&~EKGAP z0OygxHQ(rFHC=7xl;Lseka7QcXdmRH)_MY~wNTdjCBrZfUN0o>S6u>(!H$BtyseK-{%t zMC-{{*|XE7>1$TO!G*dv7;D1jH!AR3)zfukH1~)0F+d@cdNiW7%c(a_*Hp5jRy|E* z+NlZuUOuAxH3OGDIOK(=`-!yoMX|mtSbM@jw)L=?jNu5aCszP0#w_-(wH(Q{F*)9Y zMOme{^9^1T+B)Lk_Rp|!@-lwsFZN*rl!nN`cYF|Ekkx+tXF6F`gVs4*9$>xvzHFj5f?;7`M{1*10B@9g1 zKJQE#dqIr+9C#(%*Rx9vYE|(9sZ5%g_nDFYiw5ymCfR1{d%fm)lUNy=1~c&|T^^pJ=w~r64T+j@wYTO)!gLo6f{R+jQP` zyjb;cOTuC)h}E)V_i~jd%3!TR>c+26QIHU&b*DYo6^8c;Da~Ez0?e67=-Rn)_ zRSV|~4@>}FZ1dS;mEywo2@fiV0FGC>xw3XkNAU&wChkM7XhbYLP_59-;G`ow-3HE% z+)D{yf0-74XnDAG{4gr8_@Sf{dT+RGQp~|IzB^(5JL0Cp41&cDQm$M_6vAp)Q$aDN zxqN(Xb!KHy=A@6Da-D&OXlNiHNFj`}W}WgtOXm2Csc5rIc`32WcFK8Hk4?hzR?CG# zPBn+uOO7HZTtayrn>Grb5(D5iQ0)uW`pX3!_*hM)HoJHzVlS1tCK-`AV^V4gw#W z3rKvh`}JEg+d+;t0dEL%e4UAgcFGZ0TZ=o-EZk6X%T|BhjSbx(pC)R@co)QBcxW%y zo}-$`lQXOd(f6?Zb(lf(Fyx-}N9XpFp`N!K2~Eh+IbcILRvFhoBig*!RfJE!RNNrv2tTIF(<@5mdoB} zIE+e`Osi#{4@AVZzxn1D1q`oVd1EbQ5O|YQ|s_*LsPW76J z6)}Nt?I9$t%{yBYwIzKi62iLVlvc9W{>AX*XaGdevQ>rMtA(w%oow%sQd+Zr&5f0@ zr3eX+i0CC8=_@$QeDdDf;0(hM`iMR7=sEYJoEMN#EMeJIgJq()jUW$HPti!Ee z4UUdt6(87VV58lB%_VQQO+;ih?KAe-CTKD@_OMDj$j)@FRKMEH9f&0AZ{BX|1+z{D z7VIo>9c{Ud2ELV44|>ADkPk!(6s85i7IcX*^YL11=-x5?2xgpj#GYoj?tgv6 zEa{o?j$0dE-ZZdJSL--#5PqI{QAZG+7-63^PuRm(GX2o;xRXPbjgkERONTV5>>Y0H z!XEV9p!x&Tz!l5m-Cq9uj%Z$c*jjI2y0L`Y}a^vQVag{(RH*A#sbd`c?6R@Oq zm~g1UWBghlNeeq?m!3N0<5ou2Gn#>OmV;g=bq@1l_dfkIcRrww#u`b<01VT>-ZMj;Y{Ww5v-q}`}#wll|;QfuPhl?;AxF6#_;W;!Iu^; zk%l$Gyv10YA1bBBWo^(o7iJX_}(){qoN~t<~)VxK)A=KJ$8+a7kH7+ECblr$JSUbRgJh? zumion7BqPYQ5%8Dne@u9&N&sAC5z!+TJ^nKG2qetU`?@an>DSRz}D)Lt5~PClj*lz ziwo6DCted(S2~jfryV#k2695whh84$&-ppgNlJ%<@7}jYq!YID4C^d2c7}U#{TZW# zrKoYZHT$?^qE@WJa%1F{n6~1#PcPNQFzL&47(_y~RnEZ2Ol8V5w=bl|S7q__A~qva zqq0SXCHiC{YE86XJtH+?hz6I024TuO!T6)QEYS-k>y$pI2a`FY)D3TBFnPV=PPV2_ z&rlVfJ&p`d7)o<4XqYR;9?r}z;jk(kqnEx+#(0j43Lfo0WQ{LD%Kv|eZ2#( za0x4v=)XZf>4Eg*$|YD+ad8VogK~>cKNy!J;he`Ci%3ECpHktXSis*P$;lh~;XC*~UOGW_tDX%>&Zf2Lb` z&6Qa(DsXzb$G<&i#>>@lK-g?=(|>!S} zHGT+{ul^u~{LMPe2sifH5S`<{#US!pg2_p&hx>*t`^P@QoeFFYA@M!>RTh;2%;*&S z3r`e$dUzq>wno)F2KlakKn9qtiy00Cppu88I)Ux?Ynj-bOGuOLo@QlW)f!cmv&CW1ETPmQQMVAr0H@6wW~bJJx`0ecFUNH5_KiKBC&hXF6|Gyz zW1<0_EysKIrp2SOsWYUqW5T%9B`)<>7dWy<-^5-KG!lZB(Cy07u!z*~+ESEwbydqu z9vB$ZMWuze%TR>`0Z^r}Xq{SNoOA-?ymTVS|HP;VwXL~+G{C7>ZnXPKg@mCmhjaBH zENBNa-({;i8HAbh5~J1#dfoaau-&#N^&O91s@r&@rTf0G@!tC_{IKS(2XbPD)o`^h zRk|>b`NF=GVQtfNwI&mMc0^HRJVsGOLP5J@-J@Kj=#j|hu;u{c3D5=6BKrdISJ0as z2lGK^wc=)IbL*P1#f~=)wpZD@(eDV8;e=p1)(@DKD%Fne&bke%zF`zk>$@@hQNEI= z8t^Y3>|0}y(n~wjZanV9!MR;?)M}BGV*R72W!qtbb=~*fmEJco1`{8H4cy@&h*rP! zz_`TSXcdgfhlkhGZ9s+hhU`hw5`nc$sI_4brI$V|Ff3K^eGRIHk06~Wlx_9%&x(?k z^?vNey0L_iK>Ei5^W}l&?RT6?F<;W^;C@*TeapifP7eHj1EzeeP2=@FiD4V5UJDbl z9?A~Z(mpAzaZ-O{P-fgbalV~*W!f}LQh|h6Bs=@vbUE}ht(z3VT|=%Tv(f!#9g)+< zHCY3xDUKCTjvYHYZ_EL=qL|+hVyI8yn!mcD~pR{GmqSg*#~) z?I+k-t_37^ZU$nBdpMS3GvC@nHMst6vkuV>Qa4F!SwWH6Fckdna0gr!IIw zRbva^hUYdcD20v-PTcD7m@JQtstvb%UcZBX=Dvo`k~8DAfEdub&vzxpTr`!TPQb zTfRBdd47lfUul~h-FkUP_C8Xe-qEB1WyaJ9p)d8P zdp&$Z;yIO$Th6mlUL`oS1sJ-#>gl{Sj7(6Fu%pZ*NwO>HKKo6ooNrrizhHI3%`lyCRiW_mklV-|aW4t$RmI`2xp-WbCp4yZD?s=Zi-fLR6VB2k zII(>#!b`uKt}F@sZLcz2L97z4N3R_0KD~G?R>h_JnMvce=8+V#$pO&c@6DncIv*{q)!!x)jz|lI=Ha>F^4_4m*b| z(+|;}f&0@^(Ws!Bek5ps;4$lqAS;A5YG}j9R$9Tq*@u`XE|v-t12Z8c6!7>RpmF12 zVT;wO-y}QaP`@xYuvdN0zr7?(TB7_`BYd{n3aTScqB)nZn%Gn2wni%nsn%RG#zk&= z_7D{gtR(BjJ8397pmT7lYJJ%%DDjc3^0}rSh!4ORBfY`Cwcx{T{Wf&qSiYj9$u_&9F?z#apUh z%jcT6{2o{HH_WGFf$M&5g9z!zP44cD04?((?{BQacYHR-imGPSr{C8B7v}VaZOC)v zz5U0PzW_1&0O0QIyq5py`TMEoM0P*;)jPUms(cQoI;Z-ox{||Q%7f?dPLNl%S~A~` z6bc*|FZu>#q@c7dxssqI>%u4<3ztK;e1*HP&ySG9e4$I{avnMI)bWw=EJuFXFs_4#Mx$O6X4%#b+jHIxX;>rMNw8X5iAA0uj?Nem}@9 z@|aZ^O}19T(1_1IlRbYxVhkl%kQ&>zVhNvimby@WpY)msfYCLPgwv(gHNWSrgxbCc z)wcN)sL7m3wd5@|GvirQXML&7vmcQW`SA1cu`*cYNG5*AYSl0G>@)x7O1c2x`i4c1 zZw+gnh10O6$;B-#PgR=l#%z#`t!wX|5b0)bEz%R*P~5gRf82U|Fsb zGM}P`!GUYg)JS&U_3PXZvxDcv{SLQs0&XHWJ5ilF`-6n&)pW=w-+qG{_KH9pR%kxz z`$P_BgOfGF$aOW6g&V507mB)_9v|eUe_AH`9DTYP9Ax0VlN0S2xc- z$fFEuml+d|LGw%o)J%LgF|(s{;Orr}YQsAFME~R6X`6yt&B6i5gRqCQ`GGj!I$7m; zSvagfe;^3U-2|--IPnx?m5a%c$TOTQ_k38Qxl>`$t##O1r2W)x_)zp2Nb;+Ps)MP* ze6@szBrxuN?Pa!LXzEm!AakQ=dN3>}AmKS)>^8X7&zBen`hHCU?WjEpw1zvQEGA6P zqeV{7^|x?(rDz&NUmNko-nZFZz5YDEX)eDg{qNo6sV_~jpkc}iM<}B6O&}tDK?pe5uF;(AMG$m>7R4A4ZEQb z*;AMmz|xC6Jgis4Y(FAq0vdlLcx$r}=dGt9D(zGkl~r#OL#z}f$3*FK&`YshS00ty z?4pI@AKFcn#+xg}lDP+}6y;qI^>y3y#mwC{{LC)oWYTBK0TKEQx$m2o+=9m<1%$`;Fc-uWJ;W%r#Z65~)VMX%4_t1rB+Mi&I6P`V!W) zxe+u$VsUmlCFIIBPG9j~Hn2RZebmh4Xt3udziHbnxJt%tHOd&uOxhX6%=yDJn{~kI z*0^~QP9e{m)w)CYPyDdc$G0`oZWd}ns#1wX)Pe`X+N_6f*o+q^_N9sT?lWxH6=CKB zPaI~fmL;rtjE?Xp?Dk@${bJ=L1+{xY0~r!vB1avfcEeScEb*}Gvbbf2(bRy4)QtD6 zM?S{Z$T1+2PjCm)hEg>elVoJNhiUVE9x5}gM>LXRea$CjfX*}*3(2!FSKoCB!kR$UpRn zLiG$sO}%#?%!Ykx#lXe}*V4ELJp>wsr)K7lEd(~$ZnTOAV&>{S6NND?7|)ALBREJilvk__ z{T*zi>tWSdOZkHbG~f7u5xu9!kEV%ck&u-m1jY8=@!uyHdfx>z(61e@-DdWgWaumE65XK zXARrLh>2 zaP8~k+Fef2N?R{OA)2Sxe1(TFWr(BgmOZ{KaS4rYvaVk!(*Y}-ZqY0#ZH^$~bdxhI zlC_7RZ0k_ff==uCECn-T(TCS#U9bm}E4QhUN)iFs8XTMLWI2Rp!h5&7&5pa-<_ww=TsZM0DxFiVSqJ78Ahbf*S5(xjKUpG>_ss7s!uk6 zXt{)K-`Dz@Qvt_|IJiWdo1bSU*P>t+bCiCmwczyg0>=A+Ti&IN?l)V&)rcyOH}e-z zO$jkk{KmyK8-_r80IeCe!&Zb;&tfp%ST*sU<)HeBB)?GuJ@Ve(YOmd-pQO#R_CwT6 zAIFBxVL*-?&)Q%XW=qy;KyPm^;HB7xQA$H;p;lvX@bYgtTTgbKWni&Oa*yiT%p0%oJPM-<%K2oX%W zuT_@47TEob%1{!2vA6AsgnWbuG4W-T3B0?dpY)dXyJ12kdCQ#$pO!9(u^aw+AJsDP zRYF6>b)j93VW$Q@@Fw?>BfcluG|=)!Ha4yCdE4PkWm)P)k%~M_ZfpK{f0;e-60Twt z6ymbf*~QX|Mn7$msVrv|a}*fXWmhIdpdH4N6x}!W$@f;f1Y5sDZ(sD8e?tRge`+0?L9>0fr>sBJLtj#>Ru}Ytu_6y~WYQk8iztM>J8UZ-op#E4jel)t z(NqnM@#eQ5v9*3}2L#NOvkk$g%l0y?B?3=hSaYpD9({DhR3P-^zUP`Ze#eV%r#5%8 zjBWpLSe=vJdbsE!iqwW|x(L;}UR?_bvu%@|V^nsPB)-bi{iMpG-%Js+rZwTl`#7)S zswlU)W<-|e$T9imCL|ifS+bi#S=Z`^A@e_7b{;IJdu$x^XWL^008S0h z0vku1E;Ir8+n+ifsRi}nDdeB)KETON2(eutY}TJTeusA4e8};f?SkeKDdqi(f267C z)qfyqsOTvRq#a(?fjWm|qz7pKC0y|roFw!*J!6!jH3>24NKAWn}CfI?B2iE}A zuW&6zrtO!n#9{fv@BYN0a5zvp`)RU5IrYA2bL0-zmOl7Xra#$M9tAv__xK*IlolA@ zc>kXul@L-&i)Th2fSM0!zGMW1Oe>VlD&`+)dQy#cHl76&S~#&auDouXi?Jx&?HHHhh*Ko}G#lhv5O23A z^w)&?y#%=M3Om3VmsY14ex7Rf`a)9CABZnF{g9ufg~e3}RUO5RO#@WUWSMB^=oKxq z6KB&Hhfgd&wJ{6WK_t&5dQ2lS=I$;vGB=l0MS*xOK8p;|**9}3{_Dgglt2$j_a$lf z+;+a1B|N(84$+?N9RU7^cxPfvtB9|}>RTDNz#PMvVg_U3V$FTeq(A88cn_%DF7Z^@ zm!8}kGZuVL@m>Z{1mcJy<|tZtvUuqqPFaWrL&H&jgwHEH?aj^v3rQiGpQ?=GBi$!w zwK=QAz&~`XH*snc z|Iqaxy26v~r^mdJU6mv0I!JB);;ZT2m>yyIQz)m@-i7#B4=qtY_)IZ|++nzMsckDc ziRPy?Za(FBjM!x=f6VYaYNM^VhNA^ScJG7cIgCLl z$t=sgpRCZsf)SFOoYDtd+9+pd=xAA+Q)eb_zu;;KHD<+{{ST>8K4_l|I5zWSxAO9X zjT_che{%H7{Qe3A|Dm9o17BCse=6t(S)cgzM`v2g_^t3i_;jJN?vl6A$qVM|crtFk z)OMAQKK-Y#mEVHKc&BHbP`nb@wo;6pq13qn>T4I2B>~JgG*l#$KPUb#u*aYC{O=XP z{2@_ox>f(TJ#ZxH2h0w6ux$H3rtyFM`kk8qv(sYK{TG11e<+Hp`vJ2vgmUSH{pZ=Q zaRtFzgiXS~s^a1=x81)gLTYXiV0Lg{>Bc|S%zvfzKfpYHYI=@^gqMl`(9ADL%Ht)F zF*1Sd9{;;Uf6V@tR?1G!;`cecryB&r=@ZZ6J@CN$w ze@jEqQ|5p`%%q{-QfdD&B>t{||4DU-8V4x2{T65~?!OcB&$*gcLs%!=yaPM`Hsqh` zY#=3Q&j{d7`}$Wi=Wm09RL{L2I|(kq8}SE~;j}a}?kDCIxUKYD*V8TI5QP?JMm zrZUGmGwBUZ{*-|L-0LWO>kv%;q^|8WDwWlei z#`PV#Um?N~@9t`=)DKCW1B6m0Nv1{KYD* z22G9w#@Qb)72nN&@On`OS#dF>hVOgVrtHdV{2;t{YosW5zoF2s%5;}iyRf$_E0E8) zdG;2@9eD$G&ya7B#;4nDR$x#6(R+W1B9qqQ8mAe>$jf&(r#k@vN>aUr{f*I)wxzk8 zkA8Y7^Np3c;ba&h83W`9DsZ~m3mVJ&s6-~^pzacn&Jg` zJYalxJZG~G^sz1>1;Z^-Wt9+myw{DuMLZxO8_1O8I_B@?TJ!oJkASpdfF@0xYN1%xY$C)Z~fIPN*}Av=2asrwQ{4zmCqQf zFJ7wlT2_EJK}X#tm7YC8PD$D~K<(@e(U`TDxFaP{%|BddN`RnS?81v&$^6JFrDb>!kVvBz{zq)Ya0f4Zn3 zrYOVHm;k^{a8~#@f3V4-*YoNndk(?JUr zTeGcN6Q%QJ)#u*v>-{F?2*1vy8O|l~64d7Jvl50usMHO6!z*`oxT;b6R4N32M^|za zKVQ&)m{F_;btEBmw~fG)WF{OS#Z%&A<$@Xy;@;`-WfR|&9OowfH!+f;PQbqRiiW@0g7gPRiGX zgKskLek!RNBW;b!Itq`vdRS}>U27T7TjtZNQ?J4WcDY2_|Hjczbq|l{>P`{Nbr)lP z;GTDik;UKinnet_K|U+S#CssclwobLJzTf;q=d7Mc=X50_(*kBmCKhQK# zR1Y{eEhpxHxG~R*e-wF%`({02q=%gKK8hcYS4x$13bCI$%Ia4qEc7J7x-6iFYR#zZ zkos@Q#z})Qhi6Bl(_&xW$5GK=5%g^_a#VM;@knrc>a)9HwNf6#mN!u=>Oj=_rP~Q^ z&+W6joD@#8sZY{>rDA{0QfQD=rRDS!$8@t(*7+;1l-Uc56C4oZ)HgBQ>qFo&F7ufWzI40;1c}B(*+ifL{S8$KU!j&pU() z@mJ62;qejben__IR=?OW0XaG^j~3%%6O)c={&T-PtZWQ9)}=e+3*-&VNkvFNN%x++ zZY#gID$Yy@F@x&t^;fp0-KxySStQJ_y&&?O1hN#9?I0 zv*E~()y8+_MV;H}5ml;yOVw#5mcKh)Job1OR<|!;6H^=ZZAnX}I>OftZdmoveHUTV zb|h;JWC~Az6w9d?Eeh?R3MzcNJuyFvt@DOTl=x@`;-{5%Ce->byGKS7&`&BeVrBW( z@i)XkplZqn64E;RIdEWB^;eEP{9rm)>{VHp-&TXFzmgtZefJ*P_DpgcP?YeH3w7Ub zi1fO3a66XfY`RF*eJ*7uVHxQ&|1|*PnK-cOl}@;5@|x+s&u-qmGlzU5o5w5l0@GF^ zAP@=pEHMDc7K@L8iP`JuJdA0SK0ED&65>Id%Pu*@J|SZ&U-gst^>kydNrGdWEqisj ztuoJ=9a~Y$li8%LXKWI#UmVY}B&=Fvdeym(=Fdt+r}k+tNespLkj)L030Yqr*~Gy2-?@scOC$WvTyh?=jA@chZc&GA-E z>eMCcVL#Msh7Pl>I;Z)-;SJUr^&r0j`}s;jG{?Ii@g^er3hylqfiKakPEp$5zKPj| zO-MQc?s#p@-Vz44@?$XTj!+l4p>W^9Y9cLa2A=&T*@Vp&-}#k~zx8_t)txDRPNmp{ zmQ_*fkq_@%P5K=LmK%w0hiNiuRY^-mXin)a8HO?ok?X5nR!}MHW1`CgWOq73ltSTZEHx z5F+U$IV&pN7$Y!i(`F&-y8OXG5xKrs1m1RI<2B9(5o_%UYpnf7%kUwaje0&MX7DVP zf2@||H_Y(xxPg1#8TIJv$9+-G18Jh^@A!;+%3*!}+@`B$n|qZ@w|hP*%y;vaISFy@ zmA_4`MkGjpFJ-~r-f~MKt>1%rf#ET!etoan!wagH7=%MzHbzYCyUIo%z2B>4;xqb6 zZlr!y)_~8-JsXzHF>sK!3oK)@@(sZrY$`*5wOe=0j0+*pwsSu616B1TN#@gl-Rjrh ze)g=fQA*MV6fKR>>_1Z4x5YZLeQWj`yw;o^^J08>AbI5t%LP_z!!dcHkSPF*k>~cT zM3HHI4An&*dto>0;8{Yp+x$tn&ij@=f4|}u**#vrSSj!IxO;*ab}y>DnC3Q^C63K$ ztfFnM#dX~F=G!E)q$cn#~TWihg2Gvv3W_Ca((A9p`4a$wZji9U0DNvmMcF8r`BnjtQ7{T8Xg{a(s2c;U0F% zD3&LdGl#8@hyhG9ueGGzR{MkwqIZORmmfBcM+G}hlvd)*0tnIaKrfUPkTNr%0H)E5 z)UmZtoRgkL3o>KjYMwfa(A%TIb!A09;^{RVELXp;Wd+UHjc$=1Ygm1nTYclfOSr!j z=ZD^~Rl?Z_$lYXG<7rF9ySED<4B&TpF*yS^ zKp{u*Tq@Y`%UY{e2=@57sn=qH&VGP$qHHo2NL9N98P=?tp>NO zIBBOng+fBrlK84~gCGlU2`I407+>S+!}+I^-tyH2o)GdZqes%V?@(ajw-d&M*;md! zUqy~x_p$9x6y{lqDCouSR@1GlraMMCdDU?SgQNiSsrJMM#c7&I!nTq22%`sIOH=vM zY^}(~3-6vi!jgMROjv=4JhozpPe&Vu59*S@z~P*Yom!D5Z~K!kr@5H+tKbK%NnnlK z?J!`EsjRH~W{7}W8^djlL@SEcI|+sA^~8#VEX6MAZ_&&RMPF^RH6v+IM*!btjc1*F z!ncAMY3RRZ@}{dp@Up2#o#}^bP}tc{hLZUnrjItE2o!b&+;$SuU$E@oX19=AH&dph zwIt9FHoT+rA)Ds*zSp&1Cq9%70Uxu>+SO^Y-Od{cu$?lgGY0QnxSboUAJahpJX0x! z|Glv4M?mtl&Z)jSMGHBDQH4j}7->P9CU5qMW#2QFZhG0e7=f|l2#>&T4q#2!!gZ+* ze59~xm7iaJiq^CXX1~SV$~i5 zW#mj>s<~&1Pxt@+t!u!R^=kHBnP2h|eB;^b%Zcolli@KHApj4J$R&#YJBk`~T2w~a z?nNU)u3t)aza)@oc)m%|=3gIUXetARoQ8;}P}}jHp}f+Q47G5FlIR#0GHjbyA$;Ol zLSJgGtkAxfLa#)Eemi%_n9C+P_5wVyCF|y8 zc6L1m1hkl@*GRLTR#tNw%tA_`P&{Qv*D`hQ!2gF0s)0Mp=mun?j(D(`Xl)^jD14Z* zaP5c6(G94FyS^;+0L|Ec{`jQ?aQgGB$><&*AZ*(@dqI=N9}fZ`wpcD0mE)=t=f=k2GVY}qU88(fn9j=yQDB8aR35T zMe3Q)RtF5$XTvd!1I{e2rdN}F`ojV zWEslNe-;u7)-fZuncDT0PY!F$vbTA_OjoqHPoM_SS1F4!y;(WcMr|B)_orn0tph&3m!Es9nMH)4L0e^1|6i;SXan5&%meH1EL8w*Bi#&aNiXOFAZB6?<9LSXAQlMT*{zSId+u3huc`_iK!%S9QtlJ&)m{y!Ej*vpy zn!PM~cC50uP@}H%=idUbJTB-@xBT|WiQyp4h7rI9X_?OGZNr^tL@svLv9Z1OHb&X4 zbY&&=RGN3?$wzo*9@cWoNirotkNW$#{2N6?_J~YP@+0q=oQ92kaLJcG6W5@A29a;j zLM!Dx`v^Gf8g1`*`J!%bu@~A+Ji77S8XB>jW@p>8H3-{xcfG`Yb-eqemKl;lxJLEu zT#yrLYEYDZAzEc=xiU~|I@(-`Mr$vXWdFCp3<*xzv#MEq`C&k5GM99{r_e^mBC!5w zQ}*m_iJj4k9ILcr&h(m|e*N=YLY6nevR9FU(z@e^v%FuK4S9>p^zBqfy8I@h(2%Lc zJUTqtb(tu)=#vNjdxRf?pRG@Xm0+?eX9R<(hDK`lj0J(k1Y_9}?KhvgB}IrCE!y~R zPoA^y@oiOt3VZ|*u0FjMG>+9hPw0+L)5+YJADjqi@RFUjg{KI3<`Mku;qpDa zQZ=!}#}^TOYfJoKo*Oh{$k`2A!`k0oy57zmmXefKBVg_Iorg#nZ%e85uRP(IDT=$TkqRcE8OV^~vH_yYt`YiD{_a|k5W+&Sex31M3@R^wYP4L*Bv6q6! zwfiU^+1oXcLlnH{`-R+<%C4*`=5yr&xiXTTEpuXq)nJ+;!!=-fj8D0Wxq5JLeI0rY znAl$GRd9kxQJmxxh8PxVrOzt~TZRjJ)teN1T93Du*Y%x&*Q`D3n5+8wFJk=dnvVFRURJKHm%+XCIqH87Me;evY@>S)lR;FZn%w(f8{5wDk5 z+o)##ZvQ8=1tF+(SeR% zHd2l%6YGuC9M8^r3F(`^yzPdXS-Zdt^sj+1s+?&yjaWE%_gy|(MmHdk9>rnFrdKRx zY=e}TB+1lJ29wJC2|EhC#%z*bAMT0&b`oQ}Rcq^%v5F%-gl`}$0At#jra3_KUKs&>3c{JQ17dnD@0WDg-En%H|cF~LZp zS}o@4w8kc)rRr?q!m-!q+YF@6COq%%^5&~CigLcxN*inABHV+3d!(bP+$uh5on%f< zPH#)uM&!q2y2Vbo)!kZtr1S)tjMjW~4P~8m-zHMT{-IVT+fhw5cW3T|8@r*BURwFY z)6olGrnRlU5w4&p8YdUMp5+W(>gpd+UOC^BeqSmBTV1T(M4>cH5)_J{heQ6PAC8bQHJ84%x zu1jSSeU**}V*>a4wVr1GlPRc5{V~TZH$dQw5O$l5Y`Iq(d;WnlOYO zAz~Q}dD}%LVBHbg$S?0+)FiAe8uhfrEX~D0fu-^sf&lo=D%;hCIZY0a z7L3Ze8?f#ok7596DAxkvX~_y`^9VlJy4^}AlSHsUraTc$cMTrE>ugpKSM4O)1`B>8 zJJ$(U(p$Gml2m&=s7hQOg}xfyQaOI!Z(qBuDMr>L339*!#+`D%Y)RSTxcg zEsfG3DxDINQqmwGAkr-zOC+Qlltx0jq&uV)q`N~JiABSAv(Gv2+1q{g*}T8M_qzCj zF4kJlb3b>?Ip&ySxCZ?s|99Eq55GxKLPW7~KvVO@cf1SY@;c*5wg;8gyg7;$Z>23g z)%I3Fx8(*A;py`(_EbS6ETJP5c20*&^S+ zZIi0&M)YUp{IyVnNDTq%DFK3y-uoYD7`ueS>_5wlx>&vh#^bu*C7o+Zqs6mQMoAH- zoYx%|8dch57n4unE20|4pUZ;?$U{pOCIO0^n$k-W8$@?!HFZfxAXdh^} zLQZ_Re|)-rFe(Q>KaaaNyRx-n!8w+LVv*r}&syX_+RxWGGLaWAE=#!P_P>Y@zZU-f zFxDa>NSw$CX-~KEHWRD9Jws@q_!vs^=r;75JMLxdIy?{g8KGbrtMu+s_+@acqgIy`UM^K|5{5Ed5ri*=!?i>tfzlj0QW`Zd+Df2PD=0gQhfWa;ZcR!%VdBcJ8>L;LS?ugyEa zil`FbB`5sDXZ(I>|NR0qng9$keK{}Tf1MTo9O*bIZ#bwdVxPeus5}4uv_JeN_5Z^~ zw_x4jb9jgK6^uv|Yq8Un=#fh7?*^xe4$={p5+1nRqZ4UpulSFLJy6=zF0W1Rw5@|y zBfi~(wvhhMCT{p!bSn+yHjB^1soIcQ?3Oxl7qUCosKbR2OHHs9D2cjN|ILbd`?;yw z9=FBop5K9NTi0*%B10Jp8rfxGn`mcjv1fKn9woe%*WU;YCz9&*;9X)5#22R$fflo9 zmLh7zn1MYZ5dv!CPrt30lKuP1LA?1lLNF^&hX5 z^JOpbzpnLISB|pif%^Xt6W-Q%U+Q+xW~E&+*?RsqWpk9M?jMb{ZAOt=@|gT%Oq8{} z-7I8+?CTWqsQ9R&pJRUe`&A3>%;;Ji?7hC}jix&IXp#T>=-)l*{~M4$?!M6f8<77S zkUx&VzgNp|zx}Ug25m@7U!(YS`E7pw z=Y#y;M>>uc1Re?Xm5oT(f&{&GMShwiED;kz&0uqSKr&upjtvi?KAy0V`J0c)FoAk! z5%>4PBcf!iR-%n2e~u%59j7IHu-Beg znBIg|1`WRs^o*XPKm8k8{68MS|MH`+MV);7v80PSl8c3F@|UZr*`@bmEjJB2QEh1c z)2;sce;Ei+BQzY1O3#l1=RX$rb{aRf!q_%y^kP{nGrk$}?Jnc}?!f=c(LqJK4#s=m z^Xr`oTt<@%5XU?cDN>Ge)cq7wFj>)+ues9=v3>WrF9nMs2elt)wZCPqR%678Wks|(N z#2@5Rp-xk{He}C!=_2@#<49GHPRHxh)IF=-RevOym^ugdWyHf_Rz&va!WZMtIZ;s2 z?Akk7O_2t?E~CzUcGuT>H3ufu*&QCr<)<+N=YE8Hhjk~(mU^CtF%Qi>KTeNa%uwu2 zS~CkyI?Q)Q&<-qbTPfE*6?F0VQLI6NGm@`MTRG*L@X%sN{BGm@FJ(JG%Fei{$z-{v z<}kz4XXEQh9gg8F2%)+xx2)rO@>Jtsm9x~KzW4d;+xrLVh1OHggwB7^nhw{59Bl8$ z>t1cU?M&`l3}xh;e>Qrz%ENM#WFh<+l+FZ)nD0e!B52KSUQIju(Lh;$^z_Y9fpT>z z(4^7${Ly2h!cw_Pk$P?L$d*yLs`y~-;?t`qXJXjGG*{>4S}m=u%Ms7(GjnogCoK{< z4ASMN8-FDHfP16#v0ZBLrMxj{wdS0$C{iyfd}uY)Y5&{@89U-`2wBgNesY%aWfMnR69+;Bcw~IapdS3)Aii$L}v|k+%-)F8bs~R<*7g;7@ zkZ?LpCL-!18@8U?f4Hd`vWz*mQY(#q*5D+61w5Mrq5wVpfT>@&@ zll9>O=8sQ8i!CiD>ZD(KUHZQA7Fq6&G5RX(C0&@vyBv+?cMN(WxtP6^gH&9n0C!>f zit+5AeAK_&_ZE>+?~e%BnDFI6l*77vrAo{{d!7Du&k~4$Ety2qF;Df+^^VgBceH#Q z@(KDsJ9an+lnw=|w8gJV>tjFb>Gqo0F9arAA#eV*X*_Cogk-JkE~X)&Zl*Y$(7?o!cxy ztFUe%kT?v6_Lo(m-|u1J;?Q8}DdCxE;fq(XEFZ2iFTOA9M}JM=?!kT;?UgLxlJTr7 z^7YbHzd>uJYF>izaQAJ_xwXstSo`xO=vu1!b&VbQWI`4@LulD?!? znX2@_?(lCeR%}gf(cb9laWJk|X}}KM-tyOtoQ3DGd<+}#RnvQ?{RXAxqimWMZxBM? zD_t5&hJ1E2Z}KJtyv;#$_v7y!uG$S$qWx1A424c9~TY9~aJC%|yBJ_sZGsgu$#yQ%ujxWg=7C#SktV{V3jhUkPoN zc5yX7|Ij`$9xt+`$h4Z~<2?LoI&ifHKagpE&*ka@dojOH^1lW;|6F5uG~t^hh4&KhrKG^T;~+#_IQSKkn$CWs7|JF5aHe&!ck9_e1h?A zIFZ08;i!#7ZBIq^4*RcAd$?U4*;gP3W;t~Tr8T~tG{KIe3xB-GgLbwIP zd2UI4^`BK{YI!!!w-$9%R&VfTfD|}DQ|0cbX3v{vy#d?gv4B{?hIK;U9gTM)O!(K0 zW3V^2HGlI-AdVUnJI8nVj<&X|AT=$Xj8%IRILq5xmWlzOxD$r|Xkz@I-TyB@*T;$g zqr!1l;-SCE1b@Q{`h1EdO#fCt`5H&TxyH&6^$4E+`w04ri@5%4{ac{b zqYI}Vh*spI{)PlSU@CUofgZT7>2UuY$d};_=hCkN_g?HjeB_@VuT9Q1nmbek*YV$A z^?yADDx~XXEqEiWf1g}`n4y38_@+!kDv>$mMhH3O-#zc^p(elq4+bfm7xC}z@A|gl zw7>+By+F5d|JSAc^+SY5R2HH71P_Ubh)7m>4pM-J*5mD$KR9m!PQfFxC5iMy5L@~Z zBd~=|hwSMe%zDhO?t$>tWu)w{@BM!ccJL>Rv^a$Mt#S#l3KpHu8iXw8!fqz;Kpg1H zPXsoq={o~dUk7w)WIjfRcr#36(s;q&568lNriDX4l}Wbtl+jeQruK3_!FYS-2sT_= z+qS*4tGPY(z8Zh_J&?oPN#reiCgfZMwV0?IW^$cA4JmavL4W_yxF(UuR@rW)mu=SL zfyj%FK;z|~^fue)F4ay)g&ERO?QxrF-Gg3D>UjLGE!O%QbUzguzQm(~zv+)NF6!GL z6nJN(ctLJOtEsviOt^*UmGUqO=pl8b?liU{^plYw%iA9dz|X0Cu;Qi|&_;pJ&#bwk_;x6v$F=h^N@Z44^6+mB3x!_Xv;n zhQkD1eJ@f)jGF2*rqIqe_>~b9g}P*?)pIP z#BI&6Oc=2{*)!Jdy=J*6E#8gfxAx3=URKeoSFG59@-5+-4m~p8pa+W*oy8_px9wCc z@)SGyvDnUMEh3T{&8RNA(woHm$ZR+^{flg8cv`Ci=_NmCntEtDA%}iS6w75fX^Mcj zbagbV!q+z?d=(uS!_<>ky}!;mpX9yw0ttyY&&g0gAT9Do?g;X_i&!w@vgzz=oPceBa^4YFqFM5C!eLcv{YDy*`OLBiw*;b z$?%qZf5Pp});g(wOBQ5)Wis*E)JWvY(@xnkufN<4JEc;kA9K#D4-*Omo6#@z8ANEI zt1mUiaBesT_o#4QB0vcXCVOfQ$?oL(je3)hmucCek|{s}-bTO`HkUVaa_eD7PgUEm z-M&cSvQAS!z$O!VtXBgv>ViaTSl2z}!>`;KZk2^HN?r=FY}$9}1Bv4J%FWl@DLszU zKy$^cabhi(`@|-`=ybi?;gF_Uffnm%(QG(Sdz9_Mqc@rg6YhMQ;spwv_8Jm8^{(<{ z2*)R16x)$_jppCDaI)(c_DwQsAl={9!7V036Hv!AG?em2ywOk76Nmv9&TdIEhcWZ> z{PHl$xLnb$PrquqM5x&m(mRN0kqDA4Uw?FXa@q0IAcx1})Zuhb zoztklgN)m%({|Z(4u}*-GLPn|eFyPYMGN3D0a--Sp6i@Vd6}s@J}pBo<$kM-$qldV zw?9ipf^v|d>YsVzeF-wltY2%2%w#67z;0551eDzZgFkb<;yBF?h{sFpvyab+qMzR3 z|I|p;hoD_)rCNACxK7g^Ov>01dTw}uNy*ig_QOAnf-iBgO;smAjtPPcFB$y33F?C) zA5Bb)g#Z00NT}5ILW_jUsF+SJT*MW2s&jd^BOKMccuR`P_235{_j8D4>K=qEMgHy$ z_YC--x0n$uDEo`7tu<@heckt`73)2`Q&Li9gqkdv;TGZA49wxlYaPs(W0<0ka?BXU z=2@v!bE5~!Wr^Mqu@}lE!SWg^q~=>98I)+GafBO!$py6{>7;4gVA~N9A0=gXk_ACB zQAuxEL&XjUAgYKA=}4Kh$Cqr2?qoqs&N)7stI6eI@c2ajV&j&twnCQ@IGUWXIBa=nii#pR3?7plEn?g4udtd+hEa(X$V9durmFX^?r_BHy+R~R_Nl$XUgr#Fya$@|UME)Wxz2i&st^NZA z<|cw8>`!~fm2bBG;qh$faOA7|uwf}X@hgKQY*)5Z?06j3(5WgH6+Oc`q_JDY9bZE5 zpUjwQD3FeLX#<;#mwhYu-S?InUA4Urx|zN6HrSrmF`z|vzz;36yvp8xRJf2>WbY`n zHM)lPKoY|RsIMNt5UfWF*lSahWXiQyC4;OKb?t&w9K@9{q%9NU8qRopEEtdT1UjaGA!2ddIQk z4$rM1!kJ3z6ws$6`=Tv?5ovEV3XlluU+L<*r96NfQ0*pSq9%ylXdEJfMXE?q$HoDB zQA+ExRnf=m;|s95%I`gpgV4>1dm}@hia5*M=(Ff>3K3~~MW}SNyIQ2E&~Z|sA_T-n zuOn`>*Cta#fAkewsmb5$tN!tnccW4JMdlcxFCX3Lng8=mENI|Ongb@&|C=}c-#7i= z=>1{7fVp?<`$Ul2dL|mMj(dQS&+<^tpxx^B9bS7_qE3yw=C>sNqQxKrSe&PaoBtC> z7M~j}gntGmfS%LxMI==sA=D1|Rq2+4BQfJ?lFUMT2rakOR5UC!A{%A#(ZK!8-^ZOnD7uH$Zj=-pnYTbuQ)KuZE>DAEG)y)q1x@Yk2;gWNb{Ri;to zV_qU4N-YQ`Tu3J&xaqL~QmX*F>pP&59-y-mvwQ&eUT7ctVl{B-aAVYHZSOXx!`Q3l zN2*NfkxryTjxod3Y=$fOzz0R}4hl9Bs9D6Vqao5f7)0CwF$p?N-`hllv^^ zItCp$UXaA6&-P3`^R(+hG)Qrzx|EGG`iMN1fv`ARkihHB_=jdw;&!WF*@w+XVoz=n zYMoqOo>}MYtLb*VXRaLb+^~HAGhqiALya0koC(5Rs2B6&`^TO+YB5tfoE*v6BBAHJnf ziH4VmuCnPG4`g&*Tb6XC2sZ|kT}5%>*{~gevf)L=H^WO z6O)V2F1JAQO)$p!I!rCk&;cv3X+BwG`O8$AXp&flIv z#yg*SL~zP=LI=CN6f{?f*mQ?vT<{H!1oRiC?u6yWTzwU~kXs)yr$T5TW$22ekLfO| z&k{uHy?7uZL%`1FgW_=yI46Y*xY+gNkJ~>cC zMgw2F?lDR)LSkgO1R)jJ7QcSxbd{}2B-59Mh-k*hgw)Z+R)6fJ+&yxexj9jn-8pD5 z8Nb>Cs(y7XYeS`*;(+% z_{UnEI%nzi5o;=!z1#hMs2o(OH-{&}n}VJ;u?z5p)A%l`!4H~TQaAP`Go#_(#Ss=3 zuJ5KiyxttIF`6%50!eyS*B_X4vot&PSb9JdR1tnmuQaagU;}# zrPcfExB(P`HEkhrA`_M7WQTlx6gS)>#nB=23!+p?EpG{fLFCo_HpMgC7g}c2K7__; zy)BocTlV3pKMFi51`3twK*mT%|GByXKC#%*e5KTMk8T7l0R;Daf->PQuxN< z$B-sqs4@7SUmRw7dfwlZ;2{5k3UUUk-*OKIN$=^KP9c`xrqJ`cR9583yYe(1!aLM! z(Hma0iUXBaC+`7-^px#+)u)e9_ptYP^f8d+p6i9~O7*9onAoj;yK`NAYybs6rQd3` zqh8(^r=G>YhD6H`>qJQ07C;mcKNmft^VADvF8p>ru_89ajxwvql0M3nt#h^oy$S^ij!p;G}7{-n1q8w+LgJvD4Gm$qz!2MXU#&L_~Ayxq2;gWyWf9lYPi)fDKt zVICW>fOy{W@k)BQ+uedyzXHcb^<~n;qs*=j%I?^L$Q>T=?uFoJV ztkGvjI~%f)m2c5wB`>OL5^d>lmQnkCM54*;UdP`k0!e0q+We%N%l(qTZ5a`Dl^nU> zt$TGy#_d2%fsA)oE;R(HJC0d?5l(%M_{oirVi5?g2vv&!s>nz02s1K1KSIo6^|KOliqmh#rBI=j~=}2jR%aB2lT*=MvWZd&qENA0*8r6 zb46U{|LbcwcOkb3S;B*e*!iOGdtKJuwJ}yp3T?%1Hk=YT7Pmf=YySLIr$p4WJ1~i98ib*Hb#pC%PJE2ou01rrC1N^0l6x$ zLA`UNNQO?=I`x34!!hFii?(p58JBwkL6zNL2%`v?)#O&bCS)(Jd`qImB;$$(d}K`* zIsM-F?{=5xE*^ng^;efDch`LRkr5F!?7JU&0;0rTJDGL73dNrTkaReBtNS=e$nSOO zemUTw_vpPmVx<3ucRRNMSibW10&!|GDS+gf2-4fu?D^cxvQPZ2muO8?78BEwJR-)~-(L&vim}tdXle4pW zW+VA)T#bVBz?c+R-Hmht;qG$0#>c+s%HV8ri{sTGq0PyHZW$(h6yWsiH?!MRXtbUc^4J2pYY#s{kkF-?-&|h1 zAiA6^$LUtw)@E)3{d?!DDPlFO;N20xh4PEBr#Kd+kPe9AKMpnnRr?oME+$_;(EDRw zeOv_B%>q1@V#Qn)wXf6)&nr!xE-S5PBL!UcJ~h@Ur;DRO$vkL5PEv1I1XS+V7lEba zFd@RbVU6f;N?i!4MzhBs_KPi^HO^#1M*pz8#gH;8DxId3a4feG@6VKC%6T(NG{9c8)& zy{5nb$ukG?_EhJNA=2L`4d7# zg9OhIm3Ht*8HkLbAiy&yrZG5X!s#`*gMk>(Om*M}E)1!hZ1J=^^+(C?>|D+-&qO&L z*Pr=+p7lz}QFCwHDBByIe}R3EL(BUyiHENRV0MwAHWBNC^$A>2NPkRKM{Nt!6zNpw zVj|k;hEYu4;i0~>y+^GGgtr!XGM_1=_kHzJ^yW5)y%Rw*Un&9wuC>8z+1ZAx2TIhT zjTf+lsX7<24iMhZy@fUb?Y!vbW<9<;-P*NKI1IcRhqV`s>GJcS@G#U25ZCU?S52hrQtk{tJ3Owd{}euf)PA1RdWOUba>sy&mcG*1 z@L^qL%Emp_&(L3zo+vgOmg$*G)hJn@#z533H#ogzNNPw`Y&ETA8U3b)S=Cj(e+LcU z^Sk_gKbq6FR-n^y3YbSjF;KW*HMsvfKHWue2y$h7#r)SqINT>{kO zmugo8q%BN;E(ayELlnsqhKDs+gKtkLT9NvAgWyq1M!~HoEqHSMb5GwGhAoU( zFi@dq*aG&$TL$F%1NslWRXgVyHAF3g5fs_f8fqhfYp+H-{M|dF9>7Hys~ukBYch)_ zXOzUV9-P5{X#oI($Gcw>ys`xGG$NRG4@dR*iBaMo5@Q`~9A#enAB+3;;971CuG*t4 z!htLbnm&u#eg>=$)>W79ew+MxezI?t=a(9@+)Jm^rzATta*r3g|#_=)( z-r}mi)c95b9k;Q!VSR%3ydgy9l@(I*!~!|(v5pLu<9{k_&asqCkB zV&=Q$@t1Ad;>SV|Hs662F#^d+rvyvwEIxL7~m%aDP-VY^;z24seudkkotXm>q zgzJTquGK=r1&qu*hJOLvA00eAl+%7?M;V;(=8wv{tV0z;k9KDJ5@kSfVA$-mVIt|Y z%62g(?{vzlsGJSmSRc@BvDATNSuvpfE@yZedN;`Z zK#A6vrTEzw_VAqsGNXbQ9k0=RVq~3Y$NcoaC62E=t6cyr<7m6a2IZnTuQht%^3&7& z7zzrgw>O+-u|8Dw8A#uZfDckB*yR0(FHo?^`?t3vY(bJ|mf-tyl|vITPNW)T6>UI;&H2%u`w_xi12sTj|XJgfrYB*_IU)B)8&{@i`3=$Nx3@2 zo&wjB!`O}g1w}JHQr-RYy&lA^i>Ap(QhHUdB4P?acE*Q6boCC8e!zfOg{5@4gmV%? zZ0Jn$YP7IE`2i@IDNWvrF12f7cm-?+Mydt9d41i8?9sGo^*zTbOT9aQ0qUP&L1?xZ zh%XfUADI6NFF}a{;Rs%j-E9g`Vp*7KI3;H5pAVg71BX&QEMmlsFR znJHXQu+Ft=%l-3Uz-L+tAX5bgyXZjIZJLP*aOTX0y`F5b!c!m?2T~wYNvajbe zEcUV8{niG=6T4*^Tn__v8r;YEI;ybAxOA>ad?3u<3E>c;{XMp~UaWJv4NbZLOJaqx z$D|n2TAyDr5+l$~R-lB1yt8*|gW+CY`sOb&s~2j_17JtP0Ffpa6D!7<2VNmq9q>q} zW}d)$oVF(Dvo9~ypGbsVMuHT4#g?b0HGVP#x;Z-83XB$`k7m8}$QS|Ase+6_OvR{@ z^|`bupgpE34_V;l97Veaf>;6w{WUdz=HJ(*(ulzLbA}^M4BufrJFoE-WUU!5laQ5j zWH3eJAOCshT)@!)l;mY)%0T<7yWGlIa+=NE=2GF*&74tmio~tURk#czTaZfFa!*|4 z6HSBshZRBlBtk?XFpsPCJSY}(!qy?sf1B{k zZB=X_{dshRohHYw!!t(K+$bY$4zsE}dLaMdVI=L7;umDgqMF#5PR`v{+S(w@eG!t9 z^HHzy(roxb;9@j?Jp1m%#`pZ~Cz8Y{&+^n%=OVANQ`&n#)SU^^AhS#tX^#V~cz+Cr zv$l8mYFN?cM8x>|7{Oy_<_iLMCdU5FCl<6mn;wD<-{B^Wr}zcYimcS z@fH4j$h7!+>s3WBq=&Oj3&*j$I*l@-Uh$f{#;dtjeR`wxYqkV(Xz`|obmHz&kjd~5 zA#xvsG^yYA79m?Su+3iMsDm4$H*61vsoePnrbhseI61G~SfIbs8xO})>CX83QFi=| z-xK|MaG4WJ=)HBvw{$pQHI=t8Zf$ye4X3Ub)btisA{=D0TkqV6k2HNe8kG#loQaq?B0 zFcf{&!+o;#oDnCES`0bL=ngnEO?>ZjRa#BsIZN$I*e!QwR^n;YIJ}FQZs@)ffjfa3 zJYFnA%Be*VGLH<8?Rc!Zy8O95fYX3T()0o^W`|<3Xk8#Lc{|kn_}3GQDi^29Wj`zbdHuvX+c$6>{X`}kow7E(2DSF&JjriLF>3`k|r>ouI4 zc^&AaJujgI$LSW^bk$I!JMS24ea_5wE(K_bK&&=Yh-Hb7;3q7sdT-|w7WT#`ObnY6 znZ$Ao|8d2mrTDRZf3DyH#JMelnYxow&kH_YPz>NA6yXImsc=9WFf;ToG4#SHk(nK> z0i^l)@WbwyaF61BKV~<*5CpkqaPr+3^^l9YThQv_Ts|LBMOx{P<>=ed1=8VVU;w42 zWYU+6dtQz2Otx!8se_cdWxwv4}`TFdA8}42PF{)mY1Nn{d_)V{_ssdmGs?aNq1#j|R2;SZs|toqc^uq>&VUF-2poPc%pa-LaaTMn7nHh}9^G3-x%WX`2oZWiNsr^2+@n(-QPGq%CaA1mi( z0$s#1YZL?^2YT)niB&tP0#pQiGg z+hRNB*77`X3nHv4^tYI-kaxRpQ;x1{vb#rF=)(;mCKs9WZJuomZ&iIg^TXO%R$~H( zR&qBM_v~m(<)}lA#%{v9 zX+yZe%Gn-}A=;Y4y%DH#+S2Bk6Ov4f=(kGJFI@an<|9RQfSB%oiFc&VqTBEoz=;m; z=fR#?YP5zfntTt}8O_@ku3_k#UU9#j@d=%Ab0ho1M)h7BR+;8qE9>e-@L%K9r@7IM_q%=n2un&s19%Q(gWCQ zV!3tfjFpI8p6@Hf>7?;g_JI6larchpLK~W$jt95Gv}12p!f0W_8TBnVyA% z6MBsOkkoT6b1sFU3CRynvWX3J#;-iP%T%Fnf!Iz5j&@kdeOm)Tzf4SCyQSh>|7-+e zBVt~gId8E^d3TLa&}9(?jz&$1p*(f=Ho;&wR{%w+Q6O$=2l3E-io&J!3;G!k{R|9@ zQ0oYYOZv?5{b?(>ng|Ib^>KXMCfT+N%U2EljOJ3gqrS~Ufl8}n1zq?fs>_Qf%iQj+ zmDwXkQTHw8vK~*b#vtL7E07$cqn9CYa>1M$I=rk4W6`b9xvnc)59iQsO;mh$xjHiT z(#&rNa9SHuY9Ku>|NQFmed76%p6210$IEF~%%L!1%Vn^c7#E&?H|q>HusdzM8hRcr zk>S`A3p%+L(UIaX^^G#9Z5B^eFKA~#sWH=|w!-X7p!J!$Bq`h`>L2C=E!^_*>ET9! zXB@lU3_3pw0GHWSvXL2yLA;Y0GYsl05q#_XV<5OGDl`0Y#J}?}8kiDqT(k>Xs(Q1c zcgM13tBn>7jXqw}2HAk4rBd^{4Z~boal6n_pAU#7C9)K*RBJW;A6FEd&Y-a)Y2;uo zuQjgM-bD-q-%F+;&!(0i4{GaL#U}iSY{zM@zT`UXiq_S7uJpvIb6KF0Qo0`%sODvX zt~wXOJrhpyAZw%IjUNBuMb=~AKs;ejf#nN>5}yvpS^USO*C((^ zYsZVCdNoa%I5rT>e(2#m_Wp@1Ku^G`^=wW;|NPi?BvZp>`>UHs?z3jcX2pd;g@l|E z_NMYLJIHBzm=5_FX;3 zf5_xo!bFN)Jc)bSuraa~u2#zlmZ~#7+fWM-nGm3LW?x$QG0E0t5r*QtoCd08)!+Y| zlsoy+iv5|n$GVY>4z%43jF-$(=o}rcoe6gV^W_Yg7@2`=1@?DC*gQD-Qc{TwCC_vE z(~ZrWDc&NS9z{jnd%A9kvB|c3fOO8TjPE`9Ktj0c$XtV|io`bfcQ6Uvl<4xIYstG*2@f<-B^`!6y$ zQ3HyCeD<6{n#t+1XVkNC&4P3<4Bj$WGC-6hgH<%$@emR5R>s;H(acpVFv~PFJ`Sh$ z89r{LO>rL>;(Q4G{`fjyjIas=2d$dLn}!5he6{3O=N4x?xYn6^;YyuhpZgj^*nLu_Km*pVfMk{g_}D#Fi+%`Y4D4kBnI(3xe= z%w2^Agd2(X%)XJA+B6HiC*l}ArEYmn*cy>?U4Gv?iWU4lIv>!DxEUy6K|R-X$W71z zN+=aJN^u(cKxoc7$FiG4lJ| z?#!ApacLvO0QT$3%p1O1;FpWyk-|B}MN4(CT&)+O#OZ+gk07Qe8kJR!tPWgZC z&eOK3YB!#mr4Hq*$`eoL`I>wyND82k`QD-!J^v!Q;u<`H6kD$LFA?7>LwbTrG-6W+IB2*~c4_2y$c zBrICQ>A~My2UFpZdE0NH@Bm4R9KU=Wa5ppGc>i%85_QJoEauWVp!Sm(S&sw#Ws}mD% z_mT8Wj63J`6&ma&WuDxYQfZzV6BUkSpUtKvUw%!d_1YO<-lhNS5~K#$_XuaOTuZb! zo{6GKy8>|BzQ-kLx|1V&^C*<@G%mpqL~uth5AlSJt~mp)Fx1d8yjE(P)7}#s249Ik zq{~hFPvDrF(t5d=x5Nfp07_ye2G3BUhu92*vnj$hME!_yIAZ0#daht;8K*{KbnJ@R z{T6zzwCn=sM=&-*^%-L5o{#E}i$XBgfLe#r;>{ihscuK_M{VNW@?aBpbSKGQD8&*eBht*<~xu^|3z7c}od5OAE1 z9ty}d`OqTzJ+_?q{N2DJ2)Zf?AmJ)FG5mS2UvtXc12n{hdH=NSVXuN1ZBX@{8JCf5s%N}~7e6FIx-5n~BM+IdpzC$gh{K@@QKAdJcx zsMAOlQ;KvAQhRiPW(<=hNFuM0kdMLY!Q3)d^E^|7sAa^#5x|mz(H7d?eXQ8-*~%oC zjhrT-j?eok$jWlTz5I6hIK#5o`Xg@aY07ZMQ7$edhRlwjoYsp?Z>hz%WI|-=`^tXu>Qii{N{cr;3kNlC1mR*Am`a)AT?3q^djwlCO;umo zViBT0iNOiO04@q{;Ep3--_7_AaA<(H1^N)+Li&kOh+ToE{X@~P4$w~X>c}^Sx$)(4 zYeTTHOkAwjl_343(`NC4y#8kVceC4~geUnfw@xy~vnPi26GjT;OM47`P7gC}<{xuj zW~Ufm_+d^w5Cv2x)kBcNy`e(Zf7aU+Vx z5Y;~@8n?AE-i`!`gjwCm1im~51rv*hpm#&jZQ|WbSihm9HVq8BEzp*B3~zoRQ#SJP z)|wPGG=T1U#Rae=8~+Ekj&~p8^{G8tq*u9j4|LQ3)*PTJDUnUmQVNJHg#9wH03%@6 z9p{>ZxB8LU0tu~d3{V1gUxZO=EBp!5`h`94+dupG;Tr>9>5I%kNCYf0#J+D$evSqN z5}SZUv_EZR|7Bd72B~qVks%z2RasO0tU7hij?!h8WgcQV^DTC1{hHAJ7j9t_J*Y0v z;oYK@o?FA8BScV0;WhvLN%ofwZO~8RRIbm~SSIso3gV1#e4q0`JTBqP1XCW%iGhnSd2t$&m#e)l)p{4!A=6S6t3Z{y6^4I zi}8A0zOi?R=9&HMl6P)@9I(AHdj{*#Gar$VY8cgNLs*J|+2mj%N*}YVIvnmmVBFG zEE#1yFBtOl(oKeYdA)qY?&rWVs_DqKpjVIARg$eH?Ax@PQBTE|+CTxp^%jr)VW=BYF;2>8~X_OP^sc8?O!)%aay+T z*sVS-79H8|9=P2E&U8lF4+w>kk8iIAihV*y&F+nF-57FrnOTCz*kaRB++X^J&&`^? zvWw5nkjSo6fU%nxLhMmM^KeH|uSdLrjN7_P&pc&{1IO<@K)%O>6@H**Up*f5C;DmM zw7(Z@({kCX&}z=o>SGPY&IBtG^LqJ08RS6WM9Fz~-0@dphRxmZ2T!=l_bxu&4jEnp zwc2QpGoRa{qtPv7=s_S))Hq4@Z;D|aHCinc>=RQVpHR$~qR!F0koT|3wuK?H8t;#`gc&kr2u1^0 zs&EzJsi3cuQb0oR4xhh*lfs#9pO7WilK(Y&1pz}id(qG*m>;0geFa(OH3c7N?k}|- zk!k(Z5KI6KlgjBDxb*`bnYl;TM+?3EOW@B^(H4T=(QOWK+L?~hw1*fKF)_d1j61~s zaZNWDMbJF z12xxr)mlF-rK;z1n($t=LuQv4YlG6uGRyS#S-~}-3Aq;!RE4o9fJ8ot@m5 z?kuU}1yi{eo+N3e3}Muh^%Um!WE=`31+#S6!^fL5XO#{XlLOs^Rr(PWeMZtzGMVVh zK!qXo@`Nvz0X5IkIxy;D^bkjHJ1LQjgRbLo4nFR&ckkbv4HF?=k^M9 z>)2|hR6eX3;LY@w6GH>Vmy-~zR>?~$phXc)(%46s|JHo?9KZf_Dwu2nDGcY+4nW!? znT%y}G}7{yz9z|XlX!f~IxR8XCww$wLKpVp^kq!9D;D2T7a3)SN$+tWlX>3MvphvN zhpo91tLU9j+=02<)Hauofs1&?wtYyhwUi&mT0}`hxmpb_{nA%X`lCpTU&u$ruz|GV4RRZ110WQJD-8Uvk<8I+y{n0pPUX zF}Z9F9G~?}$vpcYpvlRoSMhnNFYd|qY$N+cBL&?wUwIq=oYyyHL4|w; zT6sofHD_`u^h6lYhNakIm}BFh3P-M(Z;y4~976?I)X?F9|46{%lm+R+WYOCC@D6kt@zeQW=7gtxl} zq-!gswu^+)i98X{81y93&xk(nHE9RsImMIQI&)(%?j~^6X(FC_?#UZlcl6X_D*`^5 z-6r&Gh#>&QlUw}5qf04}EpEb+CB)S7Ph~Al%AU`i+|wTEH9I$3b8ElEdMq@p=BSq_ z?DSmN+-&$$E(T5{t#qAW&(v@ld&PzSszCRb#O8I+I=9*Uy$_DFwTr$q0$=Ujza5Kt zAdgj>S0107NV6VVmB@-Wh%M%re7N@-_BwudrtzpN1$yhCjJ&UGV%=T}Kxs%6<`e?X zpOOu3JE{e1HbQ5mM?<#`j<5I@vAYU`IWBIka7$=*bnfytO}h0vj0~6IrEs^0o=NaI zy`sGz4UcK`apEcR(Oxa)#v1SD+Jk^f-WTibt7`5~%@3Mu@5%@R2^{&4LPSLS$ImIQ z_USBBW5uQyaV&U!@B5d8l{vf4`mNZ%&ptywn(NiHKQQ5{8`Xb!p!H?XPE~MJN6<&` z{@!bBzN4_!f1Iv;?C`EQ&Ym59tiO_Jq1jL20_;2#tDDJIvWfD9dH)`Z0 z^d?spc6eL@-#mfr~sRx*q<>kFNTRclRT9M2 zFga%lx;Szib_PTl5A9!lp&{4;wYmr_8PU*~w&e8|>=N17LeMI3OJ=su?GvR!Pgfs) zwwdv*3EQQ!j_S79|xMt(li; zqvc)p>zD*%XdYO87~~mzk$!O?6)%P>4F300erKH6VoD?D{#jdpqMTM^0!DlY%r2dn9cgWKoy6Q2u)x0*# z3nr%MUdnl3k%@wG-;*O=#EWA^u{4B~|9K@z;9`F;doX9e74f}PT~p<3HDOraNX#Sa z5siTy%8S`tU0tCIx*sU+XI0Hqi~hF4%s$@gnF@V{6T~mpiyGIe{UQCU%QD}OVmam3 zh8ki{zhBf)tj;AP`q%vwJw<;Mr~k^QdZoZhA$TsGG;ku__f{8Cij%q|i^R~0QGuNA zWAuH@z}>5yhf|b2Lk*(X@$#jTZQ5AKebTZCa|Q7W*lTb72>Y0+!@OZ z5l9R{2p8H98#i-*&=u$5y3Qqvi&nWFpAyHWTd~5p*MfuEj*rvJOzgO%<|j~J;}i*c z9X3`YNcu*)$66=rL#o5vYTvm&FSj04CPQnQWBBAgyQ9b8i$@p0E;xLFCh;iPwyIR`Y7rH^QD=@K2d#*sUbpeSOjR;G5vVY zK9qK4tG@QgA)3FTkjME$9BdD_{cLTs+I~PeTZs|_;j+S|Vj^QjbR=K%z<8z`_V9^ZJfW)T5Hb_9MWX}kG;2yigVezg^?hEAPMg7mOz38 z4*`M&3+^7=-AQoi;0f*!pmFy=aCdLq-KBxP5Br?&yl3z4+_BH!JI4D522GFdr>a)1 zHEY#cbLO=bARuFXPrP>QBse(z-7_!v`L>hhk3)0YnLGe-hhc=}fqy+798aL?JDUTy ztMR9+AxaiY-+&v!U6lugIx!+de?vqxUsT+6^_>t3qLZrzO$@VzJF%xBc&FHm1?9U= z(}zH_G)PnBDonFHLgit;=18G${e`AzI9~fC#7zhG4Kp{QGDEg%FXr?$HbBkgZ%E** zFP@gJ*&B7)LB};j^~$Jgq{uY305APobXHlXt_En|Kvg}+iV&!G_&IsvBMt8iu|QTm z&*tbsL=1`P4BvHRvOo?3k6~(JG z1zJNP0!7)j9>PqkrFxm}dd7EmxSot|GUaFVNLQ^{2QM~J192m8Umn>nsd-mccm(Zp_TKUtnjv%G9-sSr;4stB5stOd zARhLAu^?aZ>1+-$Ls5OV)t}tW+^^L}rsqn!FetO-kx^iIHwvACq719J@xENODPFcm z7@lh=1bwgcC}xi5NvszLKu??A7H;bEBNHGH0ldJs!@HDsq+P8k9_h;(x)MZB{2b{v z;-73JDt4);pIG9?_SpU=BPq_4By!WE(#|(^7S!E*>-aVCS%lFP>knt0W9qYthT$ki zF=3j+G?SJogT9{`o1BMIZa)6Ccy15=ZmS#CX&qY61#t8Qj*G$B&teD8=(3hFTgte+ zyPpqF)>=%Cq&tr2TD_?fBa#Xfryk#evpo{EbGV9VGov9)5w%NC;dNI!ollcG?7QTa zbn*wmI?QCzsjg>;Y|IbOn>=?8rwkY*)mE{X$w(~=X|q_CG8l@gNOTXe6v1x#X2MXcUF_A&knO3z*E!B$KJ@ z@08Z51EU}kS`(@@6fD*2FvZY~hZU(gZjKCA=8OG6OPu>Iqrf?jqy@h$Y1OE&g+`M} zq{k>lz^Yjo??I?GKm?sh;?yB~Wvj%jYvA-5}emZi<%t@2raR&j!XNgkLVS2@=df2ZoT(;DdzKXEEq# z4~dGaqDXk67F4C>xzfpqb3QJ6kXY}`+h$xIDwKO^989+Hr58E!bExfvIJ(*z9j3S1 zi4rwD0dZ{7#3hyd78<&Q5}43Re;*N@1;v+S;#m#qKk4&!Q}hE6QtXn?*O(<6eR!k| zzKdtC49!dreWAS6WL+;6c+(Y)LiY{=nqGz+(`Rn%nd`otc2)!z2X`lVgVbIKe`254 zbEo?ZeYWn*qxy0^z=24DOCse+3dc5TXlENnwQ-LeP;P&PDb{f2h-P0Ne}bF#3;SGy zgi+82>yRpkuT@^4{8uFo+Gi_ydmTYgI3r?^be{;)Fm+=F_@z(wxJSN}aercBpb3EU=`vGU)64H3ZmImMA({<^zq)+}<_~^LAfg$aX z#`Y?sk(1~jsp(^{7pM=kq#qAD!VxGW%JWm~$!2pjWXy9~oZ6KzC`_6D8aqW0fP;V) z#Y^86L$FAvv^i>A-8GfZhMpy@<>tVYEB{>&>ea8qIP**0&EES$2qK==6OmtcHeVXv z@|lm(-gfI051rF@b%;zd$6xe8&Bx88Wvl1Ng(0`p07@s{R*0*y9M@*)$PhT3Dk|mZ z43Z(QVv~3#ym47ysfy1>5av{uRD&Kzw?cJYa=51+&|kIFDpOrpx;NQ){oHf^`YGmM zU4ekW43Y$$R4i?I*0-n!fVu-g0!?bNP{8b0!gB(4-h@EBP4$yqX@SV2yg{uC@=iOM z$oQMbCeBeKUk4T0kUHZ+usR;VTU;nFp9LpD1i7BsJ9!L7%hBSCrGrUc#vEg4Vj7_M zuj=3?k3mhMtMT_YO=bk(&;G%vdPiK07K2=HeH;sGQskgojh)cR?=z?EjN)Jk0n7~W zOfZ?2rGD;a&d*lq{7>MEreAMrZHnyG!aPwo{dDS~b*Qx{@>krOJ~6*Tt5-)EwSzZ~ z0fKVD*2AcT{HA@^Vf)HQXl^eG3Yu(Z=+;TGZlA4MN6qw$@Yc>ftnpOeV>CszQ35$4 zXD58yqm9G~!ui^7$FEz`gr;wA>z~~_xGwqanOrn|8A3LC=5K)XtZ-ifug_2^-`!|^ zhkOF*Son{ms6RoF-x|XngB7>#ORXB4*cF=Y52+1DeFd+CH34#-7eNRiV#KGE93`RB zoR*i5)B^DpO3&te*_baRAn+7m@5>9g+;OnC@)&j! zUIqlUV{4()Dx(>n(XgsAzI(O06Ra-ptOMLsC6dT9fvD8qn5rJ_hM+!Rp`E_BP+_@z ziXy{!l{a{;Q*Q+MY1y?q)H+o>?fw3?0VDZ*m(z6+0i>J{bL<_ReA5y|Hy22eyw)9F z>Q1H>b|wb8O5olISR|^}Bs%yF8CTGsQF|EHt9ws^yj<(=cN6YJjO#u0Si^E-dGNOR z6xZYzhKKKl!HRz}-uJXF0+y!o7)%yd#iKA4OVUC_jyf_|x|#WFfH=&9wg`NEg(v=sa5ZGVW`3!+#bXZdA`hEP%^c9yjP59(J8bYyXLrPMzD2bq_DBJn86nmn?x? zxWnJS6u~sjXx4=Ye+Z#m-;z7|ND#hNUu@NMPYW(q@eC|nvy5{{AjGi}q zC)S);p)O$o9OY(WkcQDC_|tyw@mRUA&%M3+7v6&)j*ttCpICK!0@)6Vb2IJN?LSI+ zB+?E=v0OsV{1AEkM+;X7td}hc7C>AQ=%UsT=&Plij}={kI3Jr z8Y^tlP;p_Y6+LRbD)ACLF2defdH%}l_XnbD=dwM)yBVN<>ebEVo_}B1+Z7x&`{QNN z3j_TxH6q;v1ZaP~aTOSTvdm7-LXp|V8Q`6`l0`<29MQ&h9jE6TE2M`RC%G$m7`H3g za|L-i*S)Y_PO)<7)^j{MwmMF)>g0dtUfu36vi$s1feCG;a48j~;qKiJ9lw;vp-qOfn^vSn^80K7-n<)(mU!zq=COTk)c-UyLEh;cS~HF z4u5^WX(#k;!u3aVwJD=+Sn)F{-GtYD)o#@O;`sOMA4C{=NRE; z=Rop*7QLfwmfE$@{(aiUkZlFb;1+6^6InT?ZcXgAZKSB)tPXr zqz}y;A67yeRssz-DIASkmG)h%ru#Da6kL3Pge2f1(=8t4rl-}wr%A@c37u5wr3^r1 z?f)aPsxw8Z+>FO74ju5%7uP$v8m&dOAe1btkYgb4>V^@5$CaR3sGrMO z!|AUte8Dtsiu&w%#zOVc8|%Jfr*juuBcTq$EnmczNi^I#CD?K^a$1-~w{45b*~ZRLa> zMCd<0sF0u;G$u{!6;YxmvT}(B^RCV!WEbmA3i!VRD+M}CHI9rsGQ1Zx2=U7rCL=k9 z6g;E7y@YCdPKu^Od~1C|=1y58W?Ia)Lq;9+>eqt1L-N9qnhM=;^!AQbqZo7{o9?Na zlN*Ur<2kNN51Q`b?q*98RMbnEnI#ivO(1^kGuZ^&N;m`l|v zR9!t+2>}1_byINrE&`5*uv@fS;aziMWORtZ)=w({w`$u8x0hk0YbC!iHx>tuf32!N z1ANFYcgN+4UUf!u=E|sdrKwVZ5Yr&FEk5jLE=yinLRufc7at~b-1d8$(E52|TXU`s zOydA>ma@jnUo^cmtyz}D?H`2-Znsaem{_cryneWU*7|{_B9PM{ui5OC34Na*XVeXn z0oaYIRO}EgHXP;?Oz71n>~l~5 z?rA~L)rrU8I(_{%;WLVA7awT)JB4q;JEXI{$<<@V5Q^@Dv7cJDJG0^vjm%Hm>(GN(XG(s%Rh&DQqY*BX z+a*#!P#3J5{r1|1CrL%-1x}ndv6D_}8tr+Wc?o(Lg*xbe4^ZhlDzLV<17-RST|Ga| z9IP`-Ne}&iE`RbH`R*0wVOJXj({>o=vHb6~^GvBDrsvv*xz%zD#nBtrqngLy6_lEG zWg)BWohQOSic*Oe!bRh{#ds$EKzdAjBsxHF_rr{?3l2HcTGP8QVpvJNz>95uoU|NG z8D3!8K=$i_tL);Xlf2`vMd*M}^V4in*K|vJ`L*Fh*VtMpvg4qNaLP@VP8y+m1Bzlo zky*|T3+SKMBPjm)d0?B9Sq9F-;Ar`UJ@>QCJp!42{K3hGF9gPGU2>`#Ks%{Yo;K1C zF4vL?@U%!6E(?{Q=LNuoX?(ksc<+4Mr&5%o9T=w6Vo3Lc*F46#xkx0^Vlqc=PnFZU z&sYlFvH(_O$2fr}HA#^K&=nV|@xQloW=;DOJA$q{!nOZ=A_cpkGqy1OZK1k<)_AHRoUoS% z)L=|m=lc25B-TyZ967{X;jr!&p5THKLa+fckLCYJzj(8~r=CRy>FSRko1F;W6U+X5 zHOULN;YqAlHA|+Yy%(wWc`o8qAe2FTvaZ+f@{V9k*IX?J# z-AfM5oNq-k?|x^pWm=nsyjyth2G}P@`zh&{J(s#}y`q{K)K6OY0L8O9kPko1dm}o! zU6c=)r=hFS4l@Vt{bAgF$!RuQ+G!&friezJYs!{iGp4*(D&J#vp7S}kXA^%n)PbBh zff4KKd(lnk>)+W;A^7rE8Fy!|kuPi9J=12+<~lNecKD<^&X91(8-0OJj8_srTUb;J zxr%>2w_lhxQZ|03yBJ*Z!QY+Lpo|9!s}x$r*>| zz1K=8Tw3z}X_okb|ICmv9DjQD1h^S01zXiJPfN)&`}c`Z+NKGde(VTp2=QwZDPr7L zc7G&yo@@j)o%XSK*f!|V%i_8r`lSeZO5|Uu7PZO5F;c0O>c)}_dIH?bdtlB5B>hwr z8IQWT_I|gy((#RcpZi>WK)$HT9Ove27|wjDJoYz;La)AFh8d{ntm33dNbzOv2n6Z{ zcB+8xrCsjtrw~|KG{768_O{A4zb}erP^)TUp24^`Mq_Ip;14cwoju)f5V4jovz)f! zD~v$4lLN|17v3bEtjNNxg$NU(jeMX?S*hUtbK#2r2dRuXlK6NQlkR)J12SviP9_-4 zvTv?PwI3;OYzRju+NH+%p>Zl*F#5&5xY0?=+z`B0+MVlWaZ?ViM&v{SqIkULZa$mB zcY|tvIxkdQ-~zlak&R3!upIYA2*Q)5=B=h&PcfjU!s(#>L5EkA8FRkSOA21M4Fnpazv5@C*IbKdy|h&!Xr$=b@Er1;F*pYS&g^DX>6=Doa(nJpQSTF6K0FRr$NgwjTigVkNML zoJNDy53hx78tZ7(FMhba%Aaek0TLi3&nv5hiJwY))Y8l}D1GB}`aCZHMz{HKa7y)G zi#OZQ-&1;f3`-pFRhoGMx$pbA1?TjWtI>X z=&|YJM7=K7z=e2DuzTXwt84-jNG= zFalch(SymgK+|p@*bq>vDe5>`>x=~o%6p=_ZEod|W78PqZOEL@w_nBLuA-L0=fn83 z_a1wnosOndw)o0mevjl{Xavr$S%JFrI@Qaoo2=4ejY`$}vrsSwnm*hzDleG6wAoG8%wfqyAf$bmM zYd}9og1-nz7Zyv=^(@{z)@O`mrS;bI+dy(F<9mL)$Wnyb`x)5&adgM+(P3)i?U?)7 z7C#{>sj{?3&pq@?#q*=g?u01Ip>xhHmh>Gk@qIv-f^!@LO<+kXKcJgrk}ErV3Y1>Z zSIhmPr6&qiDT1ya>!+8fCks{M ztzvd}Pu+>IR1vrJMGahM7``)OgG? zrLPoYzZ2qEI#`F6|Aur%g-J&QIsFH`tVk7+<2t8G2#95E~TsqEt7;<~;`LZ%E*V3%spc z*4m9bj;LGt)qQ-1j zV7F>o!YmKPT*D)D_r5HSTSKw^9F3no@*jAHwrr3^5Zbp?S8X-u+1N zp89dR)d-{B(tD|NWKh50vImZdiCGl`NUUVh(9mc(IOKz{C~7(kl3$xsgM?1YtQN`? zB_f&>4)5+MHr6x6HqOW8&n_iDzw?VU*0O{4a0d5g;dPk8iVE?t*_qxH1V>wfZCZ%+?4v?sw>A%2kDsLXy%qFmos zr3PSkk_H-e+^s}Zu&-iUSSnPH7}CFj{#Gjnnk7s zQc@aI?S-G$Wng0i_w~Ya=Lz~`6av4aA`Ohn{~O(*G6tkVYb5_`df3rQ_r{Vuj6rD8 z!ngNIvJd3r)~RYTX&=*U9$TzaDUrlu+cKGrxmpfD2pT@V_ud zMF({P4*taxi#DuKt^Y>4F{2QH7WI?Lw}-!bhX4KO|DX)?zjOVE$^Fld|DBNknT!A3 zjenVo|EDL!e}dQ!v_So57u=@%Vy5nuCx=dG|Ks31B{K{<2GDduZc5+DzJ3SiPfXi( zr}o81eSsMP^;40E_*Sb$GvgO31%M?VyFgT!)dufRDrz$%2!f5S5R$|dV!$1@krR9u zbT!B=`E{qp#7<1(*Cxc;c2|o9v#Wda1bZeMAwCs$xjp!j7XexT2O^x2^nJO2|L5oX z53vMz%t@v;G=#~Y^}*em&8A@4RBkbRc1>o>Y4BCi8*?u|r^5-|+lvFX!6fe44%fEh z{~|KHQSp1?PkbE9!pt0VC4BeZbSR1C3-V;wMO z_x$;_vf-znv;x{F4sRN9adB@^WaZ@IYpgZpFtpb4Hpl5IW52ow`V zHb*}<^GnjGtAAogPpSra*SZ+v$hrS<5;eD7dB_%bMrJ-!T)KZ=D za=izPa5~&{+^}vn6ko(n_S`idDLkP^dAONA(HEv|UTV}mpUBmk@G9Lmv;WE#o21e@ zS=Hf?DE~vp05ZgQ|0|Qn;PC3E)&WP$vh2=6d2`}ybL-hJ!rozyI862-kYf zT2#yh&}O^StSXo9_~4Y7bc&*>o$kA%qv1 z^siq>_p=K~N~8#CThaM5q`5Pl5P7&9Wo;~-sR7bIg7uU_wm+9vYppxFtlV|F`EsWH z9Xj5q^_VOPORARWvQT&l0Caz}MbpI>-DuB*FMc1~YEt_(*DMx;1;g5IEB+hQiH-hg z9u0vz!#liLCLi-b`lED0Ed=^OwM27`1?e|ImF?O*&#twMyv(%ULvqDkGV9J%ZgyfM z5x=5`(;jPn+UwuL^EEa*GWV^=eT?^;Fs0J7ux+sC^v4!Mky6CQJte5zMr5sUTs=B| zzd!Ln=>WIQ>ZkGy7+;dzw}gaE9c$RCrENdYQVDJ&2vC5&>1Vd4^AExx{xE!)TH7Ey z)i>$m^NpU@c-QT+VHv0?vavCL@mrP5#Q`|?JR^Et26F-7m9e8c<9#KwzT_$sn!3ut z30R4mQw;jE&Ho3GL#fdCq|h13zru;`1ze_r`Y_v%DS`fu~zG^t4QL&ukga7 zwG$J#KlNJEeqY$%)IcXDKAC46?4KaV&xVDE-=`6q*my7YMLe$s$EL9pfOwT5uxrPg z&jY-^&N~yjLFB@2{NZ)-?$GMJsYU~BTM+Jy%Z4BSQNwf*|6U&kK@@L4XmIhrwld3r zw*7ka-@5oCw$nA$DK32{C(bXRpYC0}B~qO`?|xKUx+3T?VYDnjTARDsPyuwRN0XG88up9r!f`I$V zq^mK|JkDtbdFQ?nc}2^^A+K3G=Wh5;An3sHtfr^nN+@CU)(swgV%`XMlS7-@dcKNz zzW7~tg6u1KieA9?->U0z|NZ#jpBVaE=!jC1S5bS)P!kp}mDEz+IbqCEj1}d(l8nHx zy;DR=WFH*zWXIP1aJ^|ogI&NI`{VK_Yu12b7x+q$DpUL{Z>&|%md~IkT8oa+>0m)L z2t!a+!k6=7%dJP6Ypwo$NM(@qn@^tJwCy|jX_OS+Z*|kWE>%h^@zB|c?YmUhtS8M; z7!rWUxb1&BTPJn8GZPAFSEb+;Q==-_p8`bm{sAI>3En?PHG#SwPGs(zug;LizO-pK zu0PK@dtpNQpMd9IU;CCLbCtMX^bAlQ`!;}#LHp)SuBMIsK?8`I2;ZE`txzK=Vl6xRZx?h>50dE z{sM>aM%?eFH195_PW}mYfsKFPfbXQYb(dI19_pt(8stK#7>`hfVl^GgO!FJ_h0lqA zcP)v2=qR_52qHVYGzDpvNiu$-<7;F08G9;rN6va|8;ZVRt!id%6%LJ?PH^~pEAXFV zD3fPEooJ!xzD%DXFMqkZFfLfXGU_WtlMYG-nnb|~cXwXCv*qy`sdOUhT6-P;m6z7= zpQvCUJxWHvKdzNTCY@?6eT1I6-uGnQ`)wYrv-FWz--c4>`f~|V^I7m;&+-Vvxd#4y zbUIE&|3-P73PWY~K}>*aTSH_%6qorg2B`#3{z}2(*Ym$eKEOJGAJav(!kR*ae|`H> zs>o;(pVH|8E|YGndRS#*xRoxA()avQww1iGJ)m#+gVOzGOx&zQ42g4G4t}t-^jgW% zqMV}%@Q4}2PZ9=aDJvPk33%b$y@k2S#*4cGOg(HdZ0>pjNwUG%MZpt zM@aOlNi6M8N(OCeJ}Fu$y_(sfDC_jni@7{nv&gvOB(~0}j}LT6L>RR7a=ubKZZSEp zg+KMrJc>Vi>n-nbsP@+>%9v~tu+Z@|QYP)TdnJBS%G%=Y4rJrWSJ!Y(=8*G^_DxLKxcC&%EVmEpa{&>gkzM4LF1P@IQk zs^p~;7UXU?-Otf}WR=ph?e+C0y2L&()JA$%Jcim!CV$0r_Ly$nBzBFWteg?%g;Y9x zuj5&2-)bvey%K?Z5Iwq6VS9*^_@ghG`Qb-jw%a;3i!HLuPoN%+$4TFMMzVqo%=tf0 zlNQx&U6He6m7ZIg)@{M1bt0oTd%5J$^)+jQB9X(*hWLtKD;RV^0}#k<>|Er zd=jhvWCCtkHa9-+xC>;{FDhnk%Rxsm!?)8mDgm|N4>$FAw$*qEQwa%`h;A!BDO%5M z9gf*R=Wz>_GqOL89kdKJVgYUOr83_04t)0^mG{jIrB{+Fzm&T)AKjgE>Wwo{yNEkP zOxhtLLBJ!>e<-TMV`E`>>2q6qy6l_R+fKK~JY60G=6C(fIvugkB6)7E(wp|W^Z+6L z2h5@oFTM2=tt>#~;C}Y3_xZoZ`j1i1u=&Se$k4*F8*`0kov;qI9Q;tuvX3a$;fix+ zR58jX5#ObfU6+eDRSiJZ-w!~yA^qS_wFL`B#h*J2nXb>dnXG2D#%^_KdM|Dy!2BnG zL-W+ppR6Et-<2OXzN~s9WIc=VyBE&K>P+ni8|{nD$1xYODpWb8Sh-&^C^fCiU>$mn zEJ;j$!Csqw4=00JR`K~LoWR14^*MK?5qd%=hb0Ncyk8oTzKLof!A_hQ+4yOz?S#NCS| zeAAz<@zj5u0qg>DATeFo9j*93hWgLNJ3>Os7h<^KfN4rQTxe991YFz83ezII?}gXg z-b$jyMuDxphnJy5eGbFsJ#(a>CFU-?w0yr2WqE(5$ed%c{L zo~+CWyG@tkJlvpzr11dWS>F~_;ObDjerK3M>SNT9FL1*IjHS%e;2)!D0CFw`4OBFL z#T8|~=yACH30E*mIxa{kwK~-`9ytv*f^MkYcW)zZwB30l6tBv*79a6nUvxDIE;Wq~ ziWi6jOGkr@SgzXCoyhp7IHoj(Vca(M<+wgK-*Ii#X?N~RCTqN4zfgfKpK*)ibQ{ZX zltiB%i6-iv9Na!&+N1(i zW|lTNSgsHRE7%51GKJzi%gH=Cwg*(PQ01B_iQD^M2Qo)!<$^%v?kBkoh#Ls_u zVU1Ys_XS*?lpi=(w#COoh0s>DmX}&jC~6~j*{&%V@Mg*x(A>00XbUSv!Rg>8YtQhk zS%~Xxp3K19=GtD%=iAnt&y%@IB|yR#$LB!<(KoSER+Di! zj{{`>!;5^8moMqV_RK9^%wA+6d5qyA8^DEGK@*YSKm5l3#DJM2BcCe=~N<657C!-fBPYPbI?T)mF=&S0iXh0#^}3R7-dXRt6g=iEsrbL!Srf(8DWhef{f=El==F(c-@DLE2IC zMqRUq4*TPto^bNFkijnV{nmPMiNfi5hyWzKS|fO`o|%82ZS#K28uMvBtaEYnQZ-}6 z#eO7I8egpQ1wQyOD*fxnrIvpdl2NYPuTRg>m*?|d6=0u_IYN!La?5{?P@<`7)b-(@ zSe2HRncEK1e0Wmd{!*xfr-CvOrGs_bG#Pn(zYQh)G8Cs2)7fKN@1**q*mvrg_e7hq zlsRO{;txD5jqdfvCw?HQVQ#DFB}E{WG^n3cJXmOJN5C}Sp4o5MAhdF9{&*50Caf7u zhdi_Cn0;9bD{cj4+xq0-*qFP2vW&IcF0ejVpJ%^z02-PkF`R^o%?sAZ9=&%?+x5;; z-^7n4)rx7?rG>-U!}|~+a0@hf1rr#eKQT4-1HL8(57ZYV#8fn05^w42RZse6+s=Hd zDl{iQAKD)U9@wRqO<{()tuL+2Kbf>!EW|vy-LviKFKyDCG-!g6xjyJVI1K9*nXn>@ z{%Pg_9NPGtF**S25DVT-OLr>qYPz{ITi}~5KZfC~`;Ug0&-Jit67UpcPNSXy#myBX zOx)yh_MJ`~uD+26Nzo{H;9_;}_M*7GWY7AJ8ZxE!V&plR*&WNoNffZS`$28tSc9ka zj7eYbW;W^P+jX}J>qPP?`Oo=Y4OxWkH3bi! zcU`7&chA1LWA=F^j~`N+d!P00=n9hUc%JgO%Vq@qQD9uwS8opyn=MY8j}W~U5CCI( z_gG-!<~RYZFp+7|b{R#LM`auyeSz%bJ?1T8pEr!y~lpRO|*K7|KFbL7ucdF^9lQwx{MX2j;1I)b2E%{K{p z)~Bk_11KPJU{5fa{i3yBxMw<5%H-pNL4(w)D~yMS;!WAAMqj(KY)%&t^gt~Xq2 ziLrKRALAF5P!}XvqJMA5qtJJcF=#yaB3M~(;kkOQu=KVsD<*^IuY2!N}*>_)r0 z4o>{EA2zq!el{G#Vdlj)Rm(Z=I4s<%a%NV`w^ZnWl|GQdr%dj(eFW&wWc`lU^*C{N zU)6)jrl@WGTBVaQ?uz&klaN544e^l-3k$0uX))w*S#Csb(=GTO@a$({R9kV^y3l9Hs zk3Rv@wQuh1+{a7QQns{(zORom`&CjGxh;)E#n(1x(MX?4cQ8U>;UIEWP>Mgt3oIE* z5yW|E49ZxYZIZIeHjn-{;&*7mQXE=12)C8)6j|Ne=88(%D+#uNX?h?3!d@7mztCuKeJ{QyKzW~3$6 zcB{S=X4Bi-dwS@3)ZgGt6>fchdvSW$coZ6+xCqEmIo99xQS^v5-}Zzuq_WyZddwFk zvT3n*lX^A@950JGbjGXoW@JaP&kuqgrQf7PF`u8 zCpLGp7!EM+t3Irfrgs&UJiRmaYc-aVqMb+%+}7u??!%kCr4KI(cuCm){YaS+goevT z$UvCfzjW z&JXu7E|B{epOTiLM)|R!dik*f=Nj^Kk7U|OYur_1;`cIww*=&}EJ+V>ABGz20Lu8q zFV>?&S1#IlO}}F>xJ{)-3k7T2)0Yr!fhE*?I*?Q*t?He;c9uei_!Y)lrg!@aJYd>- zOdc=!%Uihere`a^;(iT&RRyvAjwtb#ltk}6bnX)Y=qUTM&H{G?@oshz^kUUvuSv+V zk~OJ~2a6=BKbE^AR)rsHqSRc5=1Z4lb2ODP~F^I(i5C=!cjRq1QdX`GnK4;-( z)tdFKevq$2BywM6ZU&_uKCAk&s0aEG1z@0lbfoHSJ~9yen~PGL6DnW#@xjxDGmajU zL|tl03h%1BonhDh*0k%|*8m;-T$Rn5^;_qC%7eM;3nOSkD;@Prjlr5kE9AiSP8aj- zu?Q0@>*<5z*5KK#Woe@>#Al0)Z@;Xg2~0;Zb3f~S__;FYt5CCNe;|#d#|4RPOMSL! zXhm0~^HYh%>rKx9<66uxXLo5&jVw4RghufX4F3iRunS%x+zvvSN+o~MH|{VhnT#@f zxw7g7`k|Ca$Ix!{P)JD^>6+-G?3K4@?k`Vj_a{-rxzW|~E5#639n0jL3rQ*^ zImO4cvnjni&}gmEV`=bNt+Q%>=VdDt&|)qE{(RzjLgWd2seU4Y(Gt+M4%hOcjT+5+^GmV!u&mb%idn1Bq!30U3ay=u!3Fb_Dz~JIN<;OTiFf-e zzfOfY(isthYp)KIPH`j6rzg{V;MvCKj7g?-r^dR`kn~zw*ElF|{F)y9?mTM5t`OUCnY2QwVe9T9(4m~Sy`9*mP z-XbT7Y2Q^4R_s=qr*28z9p@4j{{=1+Yvn`>SoU~y8!HD$NOSy3PfTR+ZGm!do)wkm zn1mmP2SJ)WLIu$>_Vq5&(a<+qOPW7U73;8+Hs4mz!@VB$^T})~DS~^(fANVA*5r84 z4XB-fSzbh(e)X>$eyiLi|2?BSu}_L2RsFEL?p9JbuE@tOx9P60$l*Y~G*(`Shz6`x zodBpM_9{w}Gl2x|u#P;tG|c*edSX}JA)X~K8s+?t@#%|p%?vc>`=cEcTz1eSpT^0p znSxMk3a{f#R`ogsa-VfY`2ybE9#MYM*!I4sadI!NkMx3{#PmgqSpK_xi0Cg7FeY(9 zY{vqQrO4gKl;tsxk7MakwjnJR`lDw@;?xZn!6c=l`CQI>fp0qAg} zMf4`_R~MJmCOM^;O4S&W+W0D;M0ZCfrkI@7V>dwx>V`ZJJ{_xi2y5>Pq!$>)MKz3L z#z%O{?YvEo*;+j8@2}YVw-}RW1nej-PWU82%>$cM{FwaK#B(0ldJNUa(5?fh7L+z! zKuk{yyV^NXc4xNr2PtnspwqUqpK9g>mF|q8^hEU!bHRH~C~Pe*o-`T44IMZ8`FweS7C=kt{>%)hR?yyb_d`7M z$%;Ty-+jdgb^mP<_w48m5{*PpNHjqh+r02WKgWhz?EV342r!WMQFGv17@uKd%F`#btusiWH)@gs_7Yja{ChJWD z!Y|9x*2B`eYVb>iA7Fq~A0j}`!@wY!uq};v>Gk^ymJs8sPaJaEj2xnEW_ ztQwSYTImXA!&@K8P93GB<**QE8>99PSm7y!F6ZoYhS5Xjb|M)LI}pt2w78{4gKlER z3W~1&$j30s>+hssGMyoQO#s(d%C3Mbo^#icKMPYA(5&Vm;^hJ1YI;5QsP`ozRh z>we&q`Dly3lfX9I&Rl7z)7zZToN1|OIu@!50I|qV#2vvA z9~>)}%uH~`Xj|-^xgZqookyP9j@A!t7qrKEJSw*^KB9|(f!hkjbmygFa~zo0R@xiw zhl};ITa1TyKe%lDg%Fle`Ep#%DFq~eDg*%ADN2$t;oHGFIczU&$FRt~&Q+fx;Iv%7 zdvKdhUk}w4IvL;c$YQCYzN-ZMq~9vQWAC*W8^4Gxb3y}3VJCR0su+?M>Sv3v?nZPEh6T!^bwsTXkpH}cijOzFga#lu~ z*fJztFo`L=r_$$HF3szZ{9T%-n9(r{)iIxV?xLt#S{MMb;<4rLn1(!`*RC(J*9r#0 z_Dy9f^1{ARE)xSgQRD_ePhOkso_ugK?(@U=roj{w7sD-fE{=gfcdl8E^p%ht-I<@$ z&%__}7sS9FP>}gplef3|cy;FM&nfJDduJv)(|abf(=a!vk@9}=M;I3GWUAxO!wS=b;+4tk}HP+?Q?dg@^myMHc`14 zoc}3O%QbV+U7v%+*tqgWPWD^rFCHc_Z~M&~`8#D)w*~m<1p3@vZ|BXZMfF|Rw}BhZ zYN{vW`gcN#vuK$Or-GW>2pb755J;8!E4Iv2xfnUS=kySms+4Fu5ZTmPCidF!T}Urj@im`ZR|W zJB$>nnRuNS*KSNbLepN>0z>efs#&Cd1_mt>l>LI4U)u-(w&&56uG>Eu*jGki9Oj`P zgm6b8NmWd-PBkA}9j}HnPi`EHpeFEE1ldaEXp0GhDYaV^7nq&0&0=o&$m44WfqCjm zBz#d-V2Sc}y2tTWg_MliyK>|PH<(u3=F~L%aFY%Ln{cf;u{o&jry#*8o<%}u!-Hba zr!=((Lz!V~bK1Y=XhBoO`0MX7#lLV;+*Yo{Nc(?pe3uG{xN;uAUFV4f>#4->PnirwI4xDI7gZ*dUT4J0NH1Qp3AG8J|9m*<|Jv_f z26J798nd~EKFnOJ;)PGkNkm^85L*3+OQ%XQ zQ*tVRNPzONZ*ml$k^3wr4SKMB_TY2e`v`?n*YC;e{DG-6ygTf+D-CH66F$#JUxH&k zKaVS4@9xJ{6W7Z}fF)gd@OfmYgf```t0@y~r~La~L%Y06iC*KvM_v6$<)4i5R9Jd?w@OUlKop zQ$)8mq_`w1$6uNbs7gn^Lg&|@qC?fqngGz&ot^@HVLQ;q|;`dOE-M_;#d5%yC8wd^%mYry5F zSFxK%IfUiC)w!zMTK44f&S&iPAQ!+#JEw<{|$Qd8}K}3vU zWP`S&?he)99LN2>$!judHK8yUNzgc=) zJcV=GztSr=El~*oO>GDV^S5z}!Zky&oK{wSoJY5L6-EQj&LF)Bc6N5HCD4Va?X=xB zWmJVoTK+w0Cel-eMgp-QnQG*x7w_(D^HxU zgJ+{tIVz;}=iipQa?h@jDHX`(1s4rq%uR6FR(v-|$JAx#O3DX-zb9SPtPl-Ts)S|)uOXk5X4D^cmFdPQK`Td+mE}!6a z9_uk{1!c$vB-rfXqGnEnSx^6i9TV>I;waASd>A=~p$?@o*C7;tEKiSI2ojolc4lI| zXNLf}tqXk9N=iCcv-8dOxMwtZpR#Sw?;GB1o!z5XP95kSP`xpYKq)e8^yzykaCx&# zS_mmQ7>L$d)IQv@F4%M5jd)7n(t&T%cC{L|S94RDyy^`$h8mCX4an1*2KS7;-D5fW zmUUYMi!Mk{H>;Ny&j7X#KqUNd=R9936?Cf!bdNdsC6lSw6w&msY8OTB1NWmmCIt-? zla$KjRWeiS4J157i-?5>$?^YYd;fZtCje$o!i8mg3@&}x5_~Qm+-#ixDp7ubtxPMc zwYPc$>{%(}Xx;kaie3EV3~QmHt#uoDVleiNkyu+zAoul`4M!z})}r4fr19f5LGM!Q za&3G``?U#AihnPaYJ4Z`|C8$XpeeIdHdA{84#s?8{yKh31l3P~#+>@T6f(!{LF<;Oh#fsxHf{DZ1YN(bX+ZP0SeUGg>O zz_>iRg+-2@>vDASYqy_0;={X3#{=Exw_KE4+xW5=vrqBeCi=!Ee!NNjXFEW7iFG^G zJN;sBRC;Wz_PGY6NxN%*%O5!{KeYDMpB#HB`Wr7q*hcd^<()|#BO2##-X=$ncM_H( zZ4YBII`}p~CS78r`#^=q`UnnG6i_U1l1XXUG%c~y;?YhD@(6lH=~3V-;wDD5AZM;Pg?PH_6V%fRn%%IPRqw*^28q{OO%!}d2R}{Q&mNOG zz(~~Kqq`Y4e)N}`=3KoL_`X;-_4y;5eXfBxDjaMeaFR2LZSf0%TxzzNSVOfCRyx$1 zB6C+&Jl|Rp%yXZ_nlbs}m*cS}Gk&{l$Qc82CLnKifX^TC;V+GJw5 zhn zI_jFH18P2nJiPnL(?jG>ZZH4D9C3~NdK(HxreL~y`==89w_pX}gNy`d;+fZC#UN*_ z`JAOU%Vvn4Ws5|Nd`{Q4F^?N1=*eA^a!d2xD!yd`mfguR*gkk`W|);NWv+?A?0KB}G_>)uazi0XWth)q4G|!&Gn3do2;_g^!$t5_I(OP|I(w%k&h)rOV^_dM< ze}A>)H2Dk~HTKB#+Jw}$Esdn-Z_|{FM|J}@+dv#6b}Hzvq-M2;?>^ad2JrTMeP#~i z5=*{`dabF(Jx;HBSQXKxF_yEcT^i*Z8AZMcsNgY;j%URRzejya`=lF!g^BZ{lgtZI zJ_>b_#P86(wI;BR2s#Y$>e(VI9O<*3+rf94NIbM$yBoMSxD%k%Z^b{a$cBQ$>2|B@ zK;IX9-yAzHvgc9mxmKcQ&j(oXoM$?3cIIx)kt;0ze&cth6tx^g;HkG`f}<^rA~OdQ z;)8MclG6MK4&@*6Q9pifuR;u&+Dn#N#cCbA4Z?x1l)qyJR3+5?Pq!mBmpRoECMGO) z=gP}34risk-R#(6tSk<%+z)yGYS%n4d}@&N@E5ZC_u5LDrs0;MXn7kk4~@pbe7(FY zZSe)+p`BBGUwp4uh!*v~X93_Xgbo#dCENvfA1K#)62q}8+I=V!ms=6W48&LQGuFf; zr{0x$OvAfQWL8XM&$fYBFi+tl`c^WvI;G;R$dA@}P#2#|{$QlLiK}hNSqy-VCP;Ww zp~WkD%kkm-shInM!y0T;sZ4PdQL6V1-?}D!UaU?-JS%P+>cra!(wogK<%|}W(eF!f zPG<8;LieK&SWrQX){s!);cEl^)=RN~EMQc;)u?fpH6%H>Hai&HVqdgoABx8)JlhDC zP`g&HvRRglejQ_R$&->$9WGdL_f^C@kw6~m2RD&wTs+9T7e%!qLHFbfbK%_irRs)vbu8R7#`jousqYTh32VLAY8g;YEvezhMK1FQ4<`NG% z{~d>V5C(y#M2A!73)UD{+Q*M!nMYK_Zw5+{2vFLCCd{ z+Qf-tJ__hUmjy@B(Wz|q@lNsCCZ*HUa(XWu!;8jNS|f_reR-`j`JA+LNCXBW5BR;t zYUE|~nqTz9JYU=II)CSqjFb+&SHI2(UoKkG39%3`N5sFI1@!!CG1nS}^dkv|dB6j!a)e6R}N^(P6xt0-Z4*No?X zCdjOJr-EWPdqeP{59F%Op!b?Ggw5G|ppj zakY}$aTu?&+SeA9;^OJDz;uQh0~y=p%qTMPrsCv>ad>*4LxMyR|LWh+x7EL=KZ5z^ zTBoDfE$)+)v!~08EgydIi?!$S>~?G4gMYFr5I!CNker@T-^C z1hXFes|PU-BSk79A>o<=rkjny`xieIU!HV&?L{inWuH?q_YFng>wpRi5o|CqwhR(F z`kWP3V{6^G8UvBm?|+WIm`gucNAuO)FWjWKuWcsOhk2D2l&)pQ55Aca^Uh)P{~W!L zXU@mti5(=OO~x zfdp5$*nVrx`DUEaNv{a~ls}fO_Ij7QEHT+1+O8?ct8#XZwmGBRpwtXMmTkV=&9m38 zH@ZQ9_?Y>1uK448x#Gx{e4}ime2PqSxPN0UJSrSEA#flwA(FW@ahN%+5AC85-#tN1 z30gl!+T*%{^mKLYqJ@n(*NZ4P-tE%3sNAas7Ae!k215ww+K-|3@xrG5+`6Z?^q80y z0tptOc!!(~#OEGd&t#u&aDR4S#r>h{o1K43w||Rg)VaYMPC(nD)whdwLqaqH~c!30-qb{V{Msg#LBn&m4Z5VpMq zQ$mt3tlCQ=5wIi#+R&Rt$-I6`=vTKz$SC|~m5B~`ZZ(bKg)Hu`S@>|ub?`(#aY6K^ zAfS+c>-tqinZQ0m5VJ|1k@}uuSTAE_D4N}@Z@eJXvV{R9+0FI?rS-wU*UhYf#sNl7 z_Op6sim9t4L4y9U;g-0>G|OE(CK^X!Yo*$U!7RU0_^dnZ!Hur@*4RtdbI}t@O_+{L zhX?j%#zybNkBCX|)xWfHcG6Li{tNRVku7LGlo!g0d$|C!!IGA|9*{alsWM69ZMc zbj{!_zELggKeQDil&W#d$9=(9)AFzzj9XY~CRJ&}{2^gruE(2&EX$T?MO|mvJH6*x{0*jrr-VnUC1iVaUX%QjE?1PtZKau?`11j?Il(?b3# zZ*Ie_I+utz|EKRnIXH$Ru~ZVbiTqr*HVJnxQclr4jak|nQ!S7Aapuj;T`lww8-}76 zQ&RDgRC%@NI_up5dIr_e@i5!S(p94oXgBE!gB9sz55fI)zYUu`Nd&pcd|_2Hbd;yG zws}$BjngA}A$1ZykOpv)>e%2QhiZ#x1PoMmI6!E2b-S;W%=vZ(U;_Z;rhPe8Q zqj|&KF@x=n`?ti8-I_vs2ha5hj{aZuiqeggAbep7IwhnanNdV4mE-?2se~XHJ4(0!teYob_>P^Rn z_F1;ExP{o>JS#_*c6X?h`1X$ffZaGM{db+VL&8Zj<-YZL!t(Dm%j55xN8_Qt^ruAqjBxYJ6LFUH}8XT)dhk^3uOjJs? z`}XZwaS0Aq4OeNLYEbs{1!QcyWrQbH!Y?k4XVX7z)FwkMuWH?xu_n}R=Fg825DC;( z0=)a5MuH16?5RHc_Ih=`nPEvyzaKe193Ezh3AN#f1AUjyI$f^YCC)1o?I|9MUkLs< zf9XJ&N8GI?1`)%PA^%4%giY3A;f z*I)E|7MmU4s!oX`^Odra-|{O?xIUfTfs(1|ar{_?mtUWLTxDU7Pu7w)Hj#*}-rECw zJ<`oF139mXwyi-($e9uOjCI-YANnm-nLWS)_i%iP{?9!Hz?Kln=&Y1m^FGj7?dW#( zi4I=;Sc;gpWGf}9*!#u{(lUUV8GU^cN0G+@@rXjMt`R*oS9DtJq&2!4oc={XW0LB% z!iuXaYnV1;U$>uBih~NX^m1R6Sdw(U)_Yx5v0=-D225qasjz33piB5D zZ3_8qTt5=&gi!d@*s7{_;li(pln=K(Q5XT@F0d{vTQ6DvV_kwJ_W}UMT5n>F5C2pP`2acLYrpc@v?*nmCjOcg`30^TDYoD_}o+bBy(7|NZ5( zIdTb}wgQK0nAbG|S2^2Pza7zIcQadLngz@BS;IRlrluQq;unfyvOGlSlTV&VcWyAK3I85D`#GOyvFr&_uDk^AKRV&_GwPd?-1?3BkiYMIDrGE zQsajPT2=-4Of zrtEE5{Zt(HZ+q);F!yg-Bkh&#<(p+b3}}vU5HMBR-!|)ko#(U%#*iMEgsq><=^Nvf z--VdbpZ)p8e;EHH?%g6I@3$uPQBIz@iQkJoIM>RG*lw?<@bJNtu$JJ@!BCAs+H8Kcf6LfsT+W5u?^~ zagXD_VhnsjrpgDc|DdHkS>r1caBQj!UA|9iSzrKC3eaVJ8wDjqC8$HgzAbA4xg}@$ z+;z83&z58FdNtLKzdHz*HXOu3bolCVT27Dt%Uq9AnV^h)RDxuAAagZ(o}7mH5XoEQ z57~YZdq=1&@nUEG)%uzEeC&~XSyt9b;hTv0I|m|d>=S&LrX}k_uH)dNB?%Jd_Xp6! z!7c8^`3x1OmfAk`N4gS|Ety}I#v4!>iT(N0NQSY~5e7?r*Dp_tsJa5sQnO4-f7GId zELJXx{Kf0;dg65h-9Z1&Amq`*DMkyg32GYVrXrW!%h#M)UvorC^*{nG_zSyS>$Jb^$srwPT)0~3| z@tXMlhaqLzI-i_mbR)*g0YLS>b zd(`+;`&j+eF_J8KGw-l@J3{g=e+pbnS8yyrO7;-uL*D44g&GQqRC(mH70Sgx3j)6L zX7YVXjD@?}hfBUC15)%opLc9%2Y2NdZ=sHrE|iSP^GXOc!5$jY0R;5gOl1vq&4QCX zG|Bg^(Gz(o*MHIULnSLcE;r8}cR87vnZ?z-s7x5?45Zpx>e9YFr=It;2SR?y6KT?OzPy7Bq3~Z3Wq+S zN|*b4P3d5cy!c*BhXo{Vj5Vkp=!59dJk?p}0K95b#_F9FFg@QJQsaFf1&ZjA-PCpt zmtOgO>nY)A?Q=I-efT<}USD(W+y*uV$cTdXuy?rA6O+9z!K(%j8p%a36WCD;k!Hjl zQVCXIK^S}X8LEmJ!D??b9z6q?65F;o9mxd4dn#Z%$gKU`iTr{$J~Ej}ertG1Wt@&!*4VQw8(yPzkuS zKHw^I&X|czRdiI?D_eFNOJ@p1bVWGrx3Vo*0f;RZfI_yH?!c|5<(rZo>fX&D*+L9D zq%S3A=AXP)^k1GZsrZdP>) zEVmoDnDrzj8eqjxBG5-%Q%aj=M0N}&m`3?hxrt9s?wK71YCBA zy+KcgMv@;r>_vU!Gl>#={s?`)&e`T!vA@pjAQc7`u5nwA^WW!X7ZSj+3~E1?j+VsB zs*OW9AVjNi2>`BOH2hBsSoC+cdQG8{Ap%#OcabZh6*rugV?%7^wR7{5hyYB)Rb z<56q_;O)|X^e#nh*bud5jW?l}1t=H-^b6>Rg zf*RfBNR;`sw1Zjua9eaTB zI@#^hBokw>Q!^~2F=yCc#yBj8DJxNKSveO7JLuCMxTX*0nbO z@4Z!uA!^1;H4a7!lx1smARwxp8ui^yX#&}pL-(iD;&`3**vT;+*Czi^(0pXt;LcL! z_+kx?A78n+oLzU**EY%lrq*S?T7IByiM_Tk6Rbs;LyOk6ySX>Wvai#_#pF#wA_xbfyLq4DTtF zIvc~sGfFKC&rtecEsb8t!GN0?MxZBFE{jlk#ae$A8R;04*}%J1f;F!p9psV3;r?}D zd+Z;<9b;!Un=tmKuQSv>kD@H`UF^9jnAhJN!O|grf6D3}xWl6VmhheL8!Oc*Z1#6- z%rQ#Z2xwLvMg;ncB?wFr!O)w}1M>f__L!=nWbqxdF+X0aF(*UI@tVCO@cb&(In7y7 zch(X4Xxpv{F0N6&iy6(snO{l5ijC0)WNmExT1WWFFscMcf>P8)*&0Mw9mF#C7|m)4lh5(t{b}Fum4`dj7l`z67EKIPUpFZD2UFzSxvpaKt}gTa z8a=b;HZ`+eB@Z34%{vJ_O6C;JQ1B?3A7 z1!>KQyNs+W@nVLvcf&wFk`mpGa^%$k|7ElDMi6K?#$B&oqs=5H`JnaX)oaNz*ED=~ zZYqz{di7xkIJWT~eF3L9>K?-xt>@xVZMwg^C;;n@0gFB}Z$FXyb)!5XM|r9s*{-=f zesF&vZu=t2EJ#FazwX&z@SX*5h53l`|L}uPZn2CME+2jf+XsIT@+Op_iH6=JB4%wt z5xuOTsi3|h7|c9*3+R&ElL zNB?9eO_%Y?U|O0Pe)o=9fP;Db=g?-WYzLec9fFYDH-IF9d()7pl4&i%?#DkEE&u`C z?=||LFs2_{VV7I?z|vs!tTfAWNmp^`2O~Xef`aB1OVqs~bKd~&s6{}QFfk4BrEgro zh7~zoy{w?(p3BFqD)GGJQD(WP6EOmpeCEgZnXP%dB&Ww`SzKh;q&LN}Oh>Bi9ChC$ z?T7ExA?YnwVKJc+BR9CGP+4=Ct=EGzmTzKiJIe363=LQy{*yUs1Iz)g6jbQzR3D*< zW#7+DQp>*3@Cv&_tG-DHCueD!kM;j^LIQ`iTj(Dwdi4=-e?HQ2|4}#0+XRwvm;^q5 zYGBnW>F%|k-g5p4Dr-=6)6+~rj(7RzUf7c93}`cUe|+q}FwlSVvMlW>^OyD*0P*~f zZSq$-;U9nW9XPam(0}v6-=Wr@zmR0dqLNA{xQ~Q<`JepbNUF9+`2WclC*XjX)vg^g zG2#i^|Hse%`#s=rCI0`z&C*7eQdA7_y`9yiv^yDmpG&kxn%T9vt$9RGjv>j(Q!KVEkDnGG28_hIQya;ikH z(NSyde#Sf^-z~5W=xZAYBj(*)``W?@x|dNyhMVu6w{|B25Zjw`-9To0M7uGy+B{?T_M!>)P0OF7=tCBSbTG%bW0uUFxgZwQUCZ(0NfX|p95z0amxe!qRZ zIkT=7dvM@T?*G(!_3G|Sx%BRKzZAOAa@zW~3(fhu*5^1z@bVT@6fc^>>2C4R<=W&Z z>J*m&=k$&C4QdY(KGv{|TC>5YGOSFCix`1!9|RT9nWz>6$C#<;Q39!y zP=C680N(rXq4p+ad$DSNL`*>tUz0gRsTwu(H{V^;e>hqgYnE3VXKaK8YhfnPNy7b4A1mJ^ zBeRHR%HCIA9E>klf#1M?q2szNC5G#2U_~VqNM`9^RjA6-Fe1OIJznejT46m*VLP8w zt?@foDRC@|_@+e78CMI?#t6n?)}%}1Gi&;2Z$ou)_R{|9QhXtl${hE8p1eVeq&JcFzXVlv=k452pt;TPHlNHbYAzbv|kJmq9h zlU(Eh$ss`Fopc@MKw*<~ccpjK+-2)G@oNU7xvG`{H>*C(e@cS|V_8Y$DdA%CnpsO94v)Y^d%G z@;uHwgx+EH)}L>OuPqncnDDc_W3=p#jb{eEihIA#*xT%LjX0l*fP@y0$D-Tj`Pliw zbUi0LS^^~g`R6hV?g)=m2f`15mDr|{URY|GPq6?AHt{tMg8~)mCDBG63A)AL57`9y zK>-PcfPbj&JiS~oqULhAQSiFIDX+30A0wJ-ebssj+8QpH6xhA78onR5?|ewG{kjcY ztEPDbkAnqzR_&7bgV_p&RcS=azA?+O^4A|AC2E+=200UrVs)lVKGs+5O^pW>sF4Ar za6r*0X1QA}TDSadf5N4)$;)-Y$!|$!4GVrp_lWtL)j5#noux_nUCwScS(WOIj;`VVI`>>QC96eBXjt`T8NF#=ffE z@$9K>cZ-`Z*mAQJyjIW54a?qcvVb$dtbH*ewJ*Ps>B)T~v(&`j7>w!B;tt6*eu(Ug z$?a8zMj&kftwoVEvSbk%ZZ}=)2imfjcRr#90EOJ)r*ItSwa@im306f@anb(`2$f>yQv z9>ntC1;lp4#_MdNXJvU zJ`QC)jnmoqN!#UMwo#|WXIa1|b>@v9u$!~!h|T!+SJK+=#T7`nxtWCO9Y7uX)T=mC4IJ70At7td)x z#r;$Mg8@f6{Cw>(-|$E&i9d7wE z&95rhl2=t%3OpGY6bghux~5Cq-Q8XsSzfijGrg}f**p$H-jFweM!KCqkq>(Qo+{ap~00Rh<#5U8l^wLnxyuji_dnPfxbrgr=r zvyKQ&`WVx;BA+k23FMy@Apv`}q(2X=+4M$8F@d}AP4Q_xndn8W`^@lJL_R3-w&D7I zs2ubN4zgNKz#suMrbsvK9dTXuE^PBPi{p!o8kZarSPV3jY)IqNjV?IVS~{YIlsRl? z>gsb2?}q2?*K-Cn%U#?RV>l{fnN4Jop502NdJ}iG*##*BSA1HzZJZayc*pZ$rg?jN z$iVc9nk9r@Ii5psIE8KY93_=~_|eOWoM)i#^+PPrCd+zAXDsSfZYxbYHUB=(R&D;Yc7Pwj#5id;!97g$5+RfT{c}P#o?&$&BeVN1rT7@@ z;nYNPiD#r{-8Xj|`BtQ6E!K+n5|OFr6fzmu>CCqdtey+noN%od?Jjg; z?A3jNylDyS%DVmlRcT->{Jq%tA=`ZWXp1Pdon5~=4wq3)@ADuIwCEB-xaDP#p~)|0 z!Tcf6OTU@3m1meXE7Xg)ovcc+QhRIGC{IC=*Xd?w_IaWCGl_0}%?t}j@m z*Ni>_N>30WWNa+C2;xjmNu~V=byNU7mSia_OpHnY|zEEym#_!8dYvwjAP13t)dKJ4^ooh-_u3YH(FnYbub*kv4mJPX( z%S3FaRMSpzA)rJ&ZcT#8xzTG>VUg$s#z35zb=|(Syh!NoO;=6Phl7V=OF-FT{2x2( zl@}ObaXgg~;JlF*r@4kb1*j@Q=oRn-`~W2u-p2G^+o_-74ogj1G%vqUoxeGKoUaSf zeqZ@>ZVZd2T^A-Cw$OzTZ!`h2DyUu;~dZG3;% z!FEbkm1^L+HIz_e0%8Ret>Vd9eCR8-6@g#OO zNQ7NNTH*XCRY|@m=+gdyq|0(8J;UYU-m5SD+beY}ft!NFwiC3s+#SyCVTrD<+&&TB z{;nAaLdH}v!6XxNIbr^ z6Z1Z(<37;K@dhS*ZoS}|1Y#^Fh>oiZDX7QV9 zM=NUFr zZ#|o5OPEBbQH-XG=C_YGrNm+Hl`50|H-x4FQ!&Dn8Z^CN2Q-8=79?j8IGLLaRbrVk z(ysdBSZPyQ9G#612-pmz@pbpZcqqGXWYGv?&%DW$QJUVH(Un;bi}yv*Ce2#=WxYz2 z*340GGR|AuWEn%zHLeF5!y?sqtu<-yXEY7PjA9s-S(FI|t5q*64Qu^_PEY!C9_3So zpqNq;D_sqCllrsuuC-JRFl0oL99U5Qhx_`LcgE~dv`|8o5w+k2z%_fWBfy1Z|-e~K0VSjR7O*Fh-MtQBok6#b~lpQ^6KaJ%)Up7|}@8@$J zX~jOT@CX|O1fa=NPCYoI2CMlI7p$HBL^fA4iYVC&B}P73MoIBFisawCFK$%}01aTV z26q8Vb;8&?5lo=4L9L^6ti!U04Ws2iEYBT8>W^;#QHrzh zYppsdqNT`O9?fc{iuvUX2T1)u5~}3skk8B&a((*obHil~H{VPdQ4`$Vqy6>hs1X|!}dV_rSlS7^+bJAD?S5Z!M4 zaA=XreA3cm=+xe%N3tsv%iSXVlHetw?Pya73EaKImU*T;MuH~&*MahNMz?P|+TjB7 z%I7)Z#b$~0sUj~*)!X65lzVP?;Aq)@!|sDLbiA+kte)kB#xt>U$t^ZI12YfWZQzD$ z@dpT$IuK4Ld+p-o&`-FNPbL_`ET&71oSwZ+s&!!ff>UX@-_aERM?(CV-=WbY8v5P{5m91 zf@#&13b;d=I==yJZFBu;5~a+~*K-#|{F?TgZ96G4ChRZ=dZTFjFmKrkjcQx?oJY0o z-dvN);!(F#tMAZ06GZR}G)-%?ChO)=7+CRrLD#Na<36w@PElsJP_|sVv{3KT3meW5 zunzR6!c))eX7+W&CX)NWP?^QjPUb zInI*@r;1%G;zl#5u!In>7s!e0IXwe$*u^rU^ve`^X`a57{VfpY70;RIJauX&rVNPM z(|;x4m9W@SpW}!fq7j^ z#cqovjXqBh;SKAsl1PSb^wH%DOLfRR?uD?a~(W$XBr`43W)-gWHIg zw@Y7ZI6Z~Ll4XoobVzXR{;>yC{~I;`pN#}O37~{iKXM+qU&7jms~!*?G&n38c}h2A zDts?{EH<~+yHp68X2O#KAe;c_2l8N}qP7@3Bsfw5huA7xP10i%ri%sU--d6@hT8qN zTXZd>I-D>2>3>Ar5#lZpSVo>yLzI6vmHOFVV~B!irVzcu!_4xAl*LZO12F2a*I# z(^pinqE@~(qz!yjjIsRHJL(@QR`=NEApezQaugH>j}3=&!_s)4ywVt@I8OEQ3)T`5 zwq$!8mA`MkZh5lX;gT{&lgAIJ`T{jh06rp2sIYR!r*pI0jlS0sLjANhdvH2Hwz32j z&*h7uX@u}ff#@}$B3j1~DMm^|uX6bT>l3UAzY~YX&zDcR3?+oiFSP@>%hs9DSxbor zN!nn^wO`-Zv1$Dsri!iK>ZY8FUHcJ8w>u@gYZ;c!BJ?amLd3e`o%SYJpLreb5@5~p z4B^B&L~CnwUH+RwpMqkb_47Tb;{pPt!N|VQGd-TmC7{mRfA#yN&s$>2bhgy>lGM!? zRta2Y4|k651s|&twgDIcgNk4ccVzvuy?*+D^uN2(ZLHY3O&0m6jS&g`vz6BF6Z~dt z#|&!x(rwauc#)E)@TFQWv5C&lQC>HlS@VZmCpNlbhw^I`#L6+pE!1LoX6Og{&PF0* zQtCI}vybOnvsyH=)R2HC+ltig{X+X>mR`9U^^Cu3>}8$T>)g1yD@o`ZHON=ONaVLK z@y(@fYWBi*VA(_l0FOQO${_C!t;r^@=pDO|+fG@vUHr+yXGXSX-&}SopJg0Q7ccMR zhWV}2hEF8YDZZ7;ig|8Q%TICl!hh$u^L7K(j0W9wK1Omh^<{X5YO<*9P*BGBC8=PR z!Sxb@5qheKVsw)}sgW>He8~+{TJs=NvG3z?XK@S zyH&Otfq|ytM#3?BsOMQ>Ez7$li{5?VNx)d*_GRVUi(S5r2mhFd%$M)ecDfX}-H#vq zVyyN)9#1?}hE;MXp@lk69Q$%ywP}U20+q{#c06r9~ z3ueXWmsWn)sbX98gn~|FdQBFIiMUDd_W)F(0zcxy@nPLa)Q^z;Q&j#h&oqPjJ7qh@ zINiN)OS4VzrNFoKP0m=^bH367!6k!uYhst>2T|`LFI0!-FV(f%P2ZT{Wc+VNH`-+T z10D9Ltrf?zLc(cK{cJ}IQuyp{vc2-{$$NOzQVkiJVMTW$L3f!E;sV%kD;WYdrV@HJ z4#8WkN2bOcou)8Im?tDbW`WtFmD4@$z+nn1w%{`x8l?Icpqi5LT&$EFg##Z6he zYPONHcD|+*(sL3P3=#o;w3^F;-=DYr3gZWVqn6Gg(U3i(6xLp_oFikLef4`PJ-?f(*e^W05uhk%TPk~mkLp-eC%TPRxy{UZqf%wu$e*0;A z;m_hfIQ>i|b5CCY_B#Qg2r`{c-AQLZrDoajvCn_#<;=ar;|~savluIcRp{|TJ!@u9Slmqc+kx9^Rx^;_7q!b(xSqX3uMrbAyR1^a(&OpAW(eoyjM?VYeTt+eo95nI z?8Ty58SZrI*y#q`Z-gh+@G)axv76OxRc8YS)no*>Bp#H0PchOOp|NuS3sFs>u#^iYL?R^KD)fo%=7MjP$DT){ z7J8cSlOVK2p(X}JdA0pyq=%#a57)q-7)-YY)h8mx`?JWy21J%pr%0K(CUOyxS$p~D z?XHmXIsQpcstR$^M?MXhGKq*%SePl-g0W>b{s;jCcU`aUa_D*Qs-`||Bbku*L>Q;r zj{2A^E$u{+2FB;+_;&a^(scx)_iTC%zxU5|BM}tZc)`GwVUkiG&(_DRdp-S?W_Hkyvdg@Tz33!liOJ6@WpbM% z4F1!zy@<|-tb7u8D+pg@$jI2S&oz?47bm!`R_JlxI%7A?+l?<7ID)EVz&2u{Zg_SJ z;GQ8yFN57J+^BP^tDYuvF-oJj^9k!ZJ6oDuM=lg)z`SpyX!EJxa zk-sBk=jwk^_Lfm`b!*l(?k<7g4uRnA?iwUO@L<6m3U>|e!QGwU9s&e+3m)9vrM}JS z)9*Qbo~Ot7dW`zPDA+|+?X~y1*EQ$7*J2pWIWZdPi?~0lAA@K_;u&cOx2Z1ifRF%k zmVo;~<4A94%!n>Zh~5cIsmfXVHH~6;8y{3X={|}f4n(h-JO;I4snoY0m#v^$iX!GP zt)wm$2kb(#r5%@#ADGsRp#0rmI(mCe=4IED5Lo)U88$Wt|5n;0dlk_xcTuArP}6@M za9SyTkiZgA(sKKwv|+{^lhT!}nd&V(caFuUzuY|5)}jcUDl|+#ZU%}V5EiH#evSv? zx-T4iNv%PjIB+9?7BPZLcNY912q8dN`OhdjN6w`M(Rw&WOkDR8_4*LcREu<0WSGjE zI9Vtx0eM4{btU_eX@?9vVB>GM;;!W95BXB(ns$2=xu#D)*8r{0hUT|OJXN@9W|5L# z+#;Tf@U3PR|38#2Z)y=}|J&;!N8P{qSQrY3WXm?+rV2TNtZ7C+=<%oC#II2No8$*t zLwFL}&{WL4C_h@LRYN0w&~daZOJXaOmn)6$TWiX76nK5v^<)TCnB}~Ue>?JZ3jVK$ z|GX#!Z{NHB@$b)y5Xol6+PE0koa?P)UiG;ET>sdfGVY`Qi;ntQk^!G?J|HRSZ*Q!B zz16-xBXULo9JKBtxIp;dZ}I=V*Z=(o5F^YNva?S$b*lf{L7cx%UH`imz~B50fR{lT)kpsLQSSe6P|sK3pn&;nL%YAx=l-gO{?{|>4H!B8 z-BvwAa3;*Vq z|8_?I$8Ry(frH3EdgwPHe-)`wF%~-9x&*u*j!?f}St<(nJW1S~2=CR!_ew_(X13kx zl6cO$={JM4#|)b9s?aHaJ@`J1@HPpYth6zQqLHH3HELo0FxFQ-hU2` z^-Nm(og-e0$*P1d2VhexotHbSqORxFBmB!Hsunlqy~IOw5}`y%c} zpJT9liQImDeXY)Az;^m>r*er}yp=<>{*!6=TgukcPGUBvF)zT_l{HiHX;7KATDOB{ zljfOxKsh5maWFwP|G|YyF7a0YUz7FPJ7p5#q{`NeyF=xEV{nQ?*D$cjJhkG1x@woL zqWi^F>D3;)i0xVn1LJSkM6;;QSMD&F)d6pE^CanbvJA_ZE|$h^OSaMLOIN?kl!z|; zq|$i$n{s#DAW@*DPJZ+tiCW=HHaHK=X&hal-}K>!@14K;d(Gd<2ASLwZlD;-DUn*S z`$E-VwrGk=JjSG?^7-LQ-;UZ{6_aO_olZ4gS7PA8~|k$@pCM%l1#$!nc-2rT#>?Q|6H-ZCeVN)` zYV0pD{B_$wi--!uKSAx@h$go#YE&0B#vdHN9Z#_Y~+* zG(t%Rw5n-V5#E(bWB&b##^4WkX$i|-)(hV?K5^So9qVwjo3eiVRm8YtI-7=Cgf`P) z5JemdMQ-%t&T;QF|6*L2Ia0LS$HsF$t79j_$+7|{h46*@A$iWkP~mfQS6jVPt`27< z(>N+$Cv}qpt12l=C*Vcu;xCWrOCH;z{7>s@Cpt8-- zV)I<3@MJd=6n$!T-0W9uv7g)}0Zvhf?$jf7+$#3T30?*W{Ng>p*?W^O9RqXA@^jYh zC($~9ivHuaBU1BPgc*8TEU^A%=JM~x2*ajr+#1Q6rIY)L`;N}@mXxjdblNo9I$N$N za-UkYAWX{tDTdycT%{ybW!rTl;2yB={CK+!{#L(Bj5PTZ5j)j5j1Iz|n-6D_0-RQN zb{0Co(cZRki+1Xf=r1b5t?`ADqGy2An`&_uVq^Ipx#R~zBeunggJO0)!UMu$r&G-N zNHiXI^v`f&dHSSVBGH^L8&CaKr2RKyh|cD)&K3qs52d_&M=BXc7L2@=1Z;&cBpdbFQ1)BAB(+a_b5RUJ zewSK4J4+k}A$lCH(c zjQsGV)#AYz4HF}Q$f0M5-oOpV?0co$Z?g3}vaN|H4Ys2p=t(&wV^w;yZF7EdZE?~UDQ29US zQ$HA83>Na?*{>^IPgaTPb1yQO!i^uPRey$EEX=DtPbD#6=_EBLi^SO^b%6FG&P!~6 zmj$|L0qU8tDuSVO5W)uWW9`)0*WVY9q|>HFA${XXl;vJUB$t!wyoYjcn04M+&Dq5B zyPc->suX;}nk=oB(S~TL@DBd5i1paoFy8r-hw+@Y<3tCG|04>o<5f_1J@F*|Z?^*@VzxL$TazKfgHVS6 zJq2MVgE;5T5QLlajfFx6&EvjNzV>C`$zP~lim9I)9cWEt()$f~Bkhhu6ze1@S+t;f zQROSFqHXxg%ZG%doU_$&cy2b}N%8a1D#Vxdq%+J5YeZU7S;6JK)85-e4`9{kEfw%^&XMlzr?64sD(7Qte)RBv&(EXrR16#A{IknT{C z{iuneeE`nt3O+Ls5DdnO5gg)9{cB->189FO9Bf`k%dnJzX)W ziIAu>5I>RxJa3jI2>e3%wu(ZgV%{)D1fhd-z3w*PFM9n}Tc0MzON%**+Vq{hS#$7L_d=sGiS4h7)% zkRWO~x~L_r+dSs3UZ;mfJnd!6#}u1>)T|SR-Oi4YYx}C+rQ-rwEW$uEiJoSk=b{3c zteK31eILZP^oasKTM2KVf=MkSAph#==x&oI+iF{Kxyks?GE8P2><=LZE|y7Ff4Ori zdIB67>|5RhZ(h1{yNI*)vUtcMUy4STV3EMSoLD^>_g{(ojEL+XZ-}LpJh#kAFE;6H z254dZ^K+_lY>323+=BAPE6ATu?+FOV3IykJbwkLoAf`|y!iffF8q8Lt>V5`1c3PrvD)T(NT;$Qn-t9>MBLVCkQHvZQ>ySbw8r119^ z(+r#2KGPt zeT=;W1`49zC%m%B9JTqDSI8lx2>V_3=Z-1w&@dPc&o4C0h?;$TM^lYan23_@s!|j>vao{H6jfW_`ZNWx1AWu^XjeHTOZzAh_1)#O@2QPfjh=myX8M0BKB0>2v9xe zWi`tGS^bDW`5=6^nU#P;C4PsR*2qe7%B=syP?_evP5ZMg8{3-j?2JjLwb}V-Zh+3` za(i$ep*Y$Z%0`DP?n753x11$tXueu=IG3zZBmYsXEMBnEd4F*nA0fwjy1J7^a$Od% zQ8HPqbbkFl{U9_LtCU==%MhjW9S@&pYwVj|js%*_P%=FYtx{Gp4dashg1u3p*FILA zMAD{zQhXoM@ckuTp?kWWdP+nr^J90ayJF*npsb z<*+?(kR|qeVE^I)C6fSmZE{u}2C+Lf{6ol`u72~LyxV`?%O+68Uzp0v z?Vha3hQ*Ro2k0lvjS}+WT{aPOD_L92?a3QUI2fQ3OoRfJS-N9Zm9bAp48}LeTeO87 zW#1>x=?uJuM|zg|-g|xacRWu~L(gahZ&TTQ|zL`f1N8He50mmIMy^o;n9tJY?nI0 zd`jx)b)$l&{9sdX4tR6ef`VK>kP4XehM;abR`9@yBer!LZk972B1~}sVTY0Ix*7d9 zhhc0GH!7W;Sc&cx-Zwj+^0L|hs~WE&wc%SCvRTnKyVkeVjQ!9KF2u~b?IwA0R08yddwL#_#5S@vcw;+QE%yhO;y|-98fKI87 z=`I}BEClBm0p!FxtfT5JrhV+kV)xC5os4fzJ6&&}{b?%s+Fby!b@O!jKv7HfdR4bm z_;(9{^=sc{DM6jS_fPQ^KN~~)Mojw`76Q^l6xlzl{4?(!sj?i3ggfWnWW2QGkxS-c zO5_)D)DO9E)2Ni#@U>V4eB%gNNvYncT=V27=i#=jn%YKxYz<+^yt$t`?s9G=U?@EQ z$;~v;khq@N!5gYq&^on-&GpG7;epIDkq&?XB=5A_Ygk=t4qP-yBS|oLAxq(aLjBj6 z;A4TmRJQ#a&0W3}_ir}4o}=k}v93`2WSw{IA;W*NPVn4r+lM2Xrz!m-_os)-8VH~0 z_HT)KsKU&QVmKVP4>*k#{H}-q!9Sjlv%E|+DrOh#M@~s)cS1zd02^sO^yMnOtF8R%Gd{L@W z)-`q^DOG>CIkM)uJrX3FHXlzooVSx}toT;ov-0UB(*I+XzTMPUFa1k)C$y=ms{?K= zIIsCpeEO(qp$$(gYDbfI21P+zgGnoihl_5p;eY%)fz~wf{N`Mcw2|zG#otU`@(Sa1 zhBYsvf<7Z13~V!KnqJJjb=*6{e4WM#^k))+5dRGO2l}QvX(@Xaa|!`K7U2KppSBP!q34ApWETs7OqBE6nl!uOc9u$!gY z9d*5A$onex`k-U+KF7jJrbnG0cV;7Y*rs6ru4_KpL|xvxApj{9x<=6laEXN?GA=cq zCbL-9%ydl8dyHIrv;NsNCa5jN?pBFuTiQt`;R~p3EXlHtGyOX6;XPs2E>25~R-2JOad{=*} zz(`PZi>zR(=h|ki=$kCMda2i2C*MClqBCiL1uQ8yu#??}oMK`E(|9YE0L^4PZBcg< z0rSVw2i^w`9m<;SlJ|-SIF=?8-K?a3arlv_C(A9nv|`9g%gyfT3iAl$*DLH(P}aN_hkD2W>w$^ku$0@e<_hW2m3X$Q{7@T8=|2x zvT6Yg+#et#k+qX}>1mOln$#3iSO9f;*G&MJSl>T;xFyZJQ^Vi3I<;MsOCHIF0j(~* z_fy9fBwWE`Z!{4wXxzI)DR#aC;JOa0R+3PagLKpVz zD?)Sfnpv$KBqA#q6^B`D2g+V zjzR%Ck)J;lg|&z%>L$#UcaG1zp$xmjMYiL`jD?ess*7JJVB}k!{X(rgO4HwBG0c^} z#aLYWIm7h4E-8LL;fl?10Yam~Jg#<=CY>VeN1g5F8{?<5&DhR>88&fWf_yj(Wuj{_ z_vJbZ=>2@7_1yRR6+>p`J)FPm!G6v!MNwBLFO~ESw{xiEv^0ab2dH8%Z|%6G*x#I(+x{nVqnjQ&KwywK4;S zn8FY&=}~upsw6L^&8?@>-;=Wl)#PQS5_ghT9gwnFCvN?ED2*iI>&7QUA?2LI4+H{Q zBCaRDcX_-6={w?R;Jh7O(2)M{RcOB1zY-!@?kX-dZy{h1v1i2S>BFMicjzErU3Egl z?oaW4^sX8J%*eBT+acWxaE^Ds92DKKrzf<+fz{m+(lsm=kV(EV%}h5w(vSOT@fUpRLW(B=ft1DxbII;1qf51>DXCfhs&- zijF7_ufywG`~{$XsWyw~Ck#Jr4Ngb1h2i^ovQ!qK$P|%G!-Ivaqit3?)hso^3KT); z%x~EX*eBs*KS2$t6!7;ec^i1)0;nsAJIx7CgtT+Kf|awT17}hv zdn8Ct(h<>mJvJN9>tiEAY&TkL1$Z=+6P{iszKfT|yyjc0UGgO!bNNiDf67vJcNU2L zMEp{xq`27=ZG3;R#rE8YQfkr-xnC%q5V|vq*IkCol@fp8VvWo2a z2+&&lO1gh78_u{80X6l18B_nzkzRmSL_tDgjg+7I9-?#_}k^g3P)P3 z+kdth&jUa)epl1kF?%E z$Tj!|u(ZKgAnMB68c;h@a7qDz$%5~eFG>5@dGHIUQJ?pOQU@=Ic@ZPUSHoUu0xy?HH zDps}t2hSp+9wK&qICK`*_2pBLE|wxH_i~|!LlqbhH>SR8z)L?G{Rm>+e2|DzT&D(K z?X&xzOIHysD!jcKT;ZjI5QmcQ-mA64L9xm%FKoIcN_C&k z7F`rP+FK*B>EijbOO^GIIT`Y^&JZ>t{j*q|vhL^;{x=d>?pJ%j3Y$SAz&JD=7xX3O z-X-t5&7uTG(t0$3)OYKp$q&DUyOwB>0yWk{Zh8jPMG`AXrs*KZ>OLm*ygIEdWY(Xb zmxA7nN$T3Z`YmrCs96UllNlgmYRde&3XgT*a8$V3u$6CN@dswZU}bD#IUFeRb3#O+ zr%1>y{J8_HGRb^&_pA5SbP-jRkAy)ol!!-8%k^1fEk)$umGG7mE@S`D7%x?m952HY zUeG3~kjDt!alQo@6A=QF!O+<#25Y^FboE5KAOI-k)9Ij^HM`x99~5_-bx#So?fA;X z_yXI~=6bOtQo7gfk@2G9U{!Au=MdiD{0lmQbYN0HuiM0#xBcr<`!Av5%vZ?}P}`z) zXGQ)0TmeghZBLb%KE71}NRaU9%BgTgeUsq*ZZ=+>=~6UVM{&E&l3I&q%XqHB?@p(t z;)AsoW0kO8=x-DO9MB2HO58}^pqeG>PI`;e6<@M8uii6CXMrpSz^&H^tYo9E1qy*x(blQJkyh*Hh9J%Sfc6%rJKpFA3_%F@Wpb5BB>;FR zzqNvn(4#ZOE7cUvqY{}YMk&k5D!%`XiS&|1ucTSIVtBK`(;xjvG>{V{SeUi8ihwg@ zh|@ctYSdTO;G-0%dH+!ZM;whSawNSQ3C z(3b7|F(rb#JEFdmuqgQ(#ZX(Q6(<7mb7T|>l~aI&Rf?PGvmKYAsYigQn>Z4)olTPLB z>3p#{_wAKnm_Ih>P%aCqLZFadmm?0Y)_OAt?<%G9YKjG;yc=EevbymtaG!$*7FTq$ z!&XcWV=G~$=l^bOsLhYVjNSyAgRFT|7g`tE*%vK5Mj?^HkkG1VxpdB2yB0PN2KDNK#oW4L|b za5Qmd=atkEX8b(N$7%9otE*tG+bCW}d@g0S4x#o`4yu)Ts?_Mm zj72>W93=jxsttFNl-8M4tP%v~+xNj-Q=~KVHP2@^p#^HDPiNV_NkiMiJY~pxMAD+g zNmSw3ax_sg6Jq&7evDOXfG(u)4&M>vIZi$K1u^WxuU2fTK-K~UqWecucMb#`OjMdL zV}1y_v&PSDp#rUL!Qai2o(WatZevH$FJk)!jm{4H3zacONzBSh#&-b`e=OADM;t#j zoqo>qSm$5v@4X!`-8@}XqLcExCNz->HYc;@jHQ5?YSpH^Z0|iaIJFfTr*I-m^#VO8 zh8n%ev)-CN6kZR-RL!-1hdoJ=>Z}(<*HoT6B&_;eAJJ1NZ&ygVI)-PYuyG;;3}X;~ z$@(2QIVUxrcig;VyT=i@)7fQ-AT(H_WVCFhpHk{j`Gb+6QmXE@N*}xM-^0uPn^ucq zjY#GOXrr>LGGNOYn=LD{?sZo?7!Zry79bN(`A*cNaA6=+^OR(`e@3fUt#xftLF&g`ZD-HKS5 z>YDbtD8R(rd_WEvPQu1p|jc%qALQKe55AvEn6^1|W z0kMX2NEcs}%oI|Uc>y+Jxw^B0G8~&)J$RAbSl3MWr2S>Rr#FR7;0H)N6zMqbdLw^; zI$27zMir%71?kp$c4#}ZZNvQ0^b}b*3A6wzn5WFKI`(=P&IqK#lZi2WOTKV;b(2+} zy?OjF>;PRH|l*j?01Ty^8W?RbE`PmcJ`DGaK+_z_a*#1E!~&7 zJxH~{Qh&eVwJS_Z0qhJ^7_+f&%FvL9G?$q5g`?#o=*7Of^d2y|0oSJtr`Y zpow~_2*Q8K8=AHvi01vLfg-^QwEBa(3WOgPqXjXr#LtsEV zmDzexsu=qGtABLKp?UMOQl0nFqNj-2A@aQw!4N+H6QP?={UDdFOByo>8YZfc!51&>Vc#3|nLXqj;rWqo zx7H%v>NVXT^p3PRLOh5k28*{Yky-7>@_dP#-;UQf5Yuup*q!t22q5WoD^c-pKNu7= za4MHh`Z!83lqhVTW))tuY4eGLOU~S`I=jSMhQ2d_|JSgN_>D zD72hqAKA9=FnOSufk@FCflNHDO^6xzLLU(%V}WwatWN3O8=k)GX4Q;5j02yyn-Ceu z=&s6ttceVnkiUoe#k%4*W{>enK;KlAhFU$cQyUB)skk}` zewOpzvi!r%fi;R?`?F2uj>-4HQ>Go4Vh##1lFa;0(D@I`)$ytHHEsN%b+@)usj2Q$ zt{qMdh9ve$k=PL9iHW$WCUbM?4g`Rkx^?F*^ubN0M4J9u9Am3zlK#pQA zH&|2?9-i!U%53~OdH80G4Q7Ee4pY7OcJUKTZCGKP&D@&4#&g@V(pb#Pk22H$#P#8Z z^iF5@5|KKk$fCvZx^)IHlt5jA-;^Kw?PQEyiZ0jR36BrW9*%+uFAj(%h47rXr7xYw zz3+P9X{e*i!P7kB<_yUUrY@PUNhw1aGW((*J@ybsSbrg5fKX`IvJ)9{fju;&{XnrW z#OtZnp}b*X4w%{8I8!dw>ch`ca59#}N43<;!rQ%RCS%jKi}%Z; zR=ahf$pckKBoUX00hHN4=u$q&Lg34ua{t2m0J%4{!AS;90hhjNe)ZRs$|*co+-pf! z?8|_+rqhGW~_^ie%P zn)d0ABBO~413EE-B+_<@QQ!m854U_Jgm%Muo8`|aEDB&wuM?F~D>U?vVca(90gdQe z>HS{^ZHbp;LmL?*vAX#pQ!0%Iz)aUd)lA}WS8PM3bVxZrr4ddt+itWaO#=xO)=fEn zP=0Fh6+Cy<G2p$K^YlpnZa1wkWRZG^=V6e^>#2X1i9b_a9MT$1 zuIG83@>vq;*ZQgFw~RGHE*sWe52s^+YvN5{OCz?-)oP&I2FLuD`ytLE+aOtKs-W@N zZrFinB7UZ`sp9r77^JOH>b2YbgI~(vMkyrYx*f%XHk(ehP%gB5hNj@v2}lXV9yh$) zJ>-&HHSnt5Z1?+qLd(q2S7d7c3DuNBI)Y04;Wob6;j;x&u4GaEows!Be>=bT5Kg5f-XL#abh{kHU%b!la(qr5 zgnY7vM-xAUpdC>?FCCDkMTtFK^UW!BY?be27UsSjl256=`^M~%wzlxNZo86Z z*P2$pNrYcV#)kybtWuHMUA){{eSR$BvjeYvw&Ty?Exz=!pH4omlbc30%8I(b@pjbi>%{ewe9uIX_Vm@Z6YvE>K(+l zR_}_oZ~OyBJX`gKr*89@&dWY~_#r>5cfgB2SjanrDMS=- zGpT8Annt^tb#QaTm)>tfnzf+SCr}$2PZ9J+0tO{9P@3Z*z-#lP{*DcSVvhdtfs|1| z$s3lfh1y49aYQe?KLcN_PLRJNZersT`k^OAH_yk3^S7?bvj{bN!LE^?R%I4<=124F z@F&k+Ri>^IdU|(R-Hr7y5V-eKN=AG+dDpgqK33Rn05esfWYJk~=c#^&wxPYPbk<>y zJGH8JKi@&Rw(?}D8+V_8@-juYvyACI1DL>vTa$`?C^{>Kf9zu>TvKqjmkNC5Uj~Bx= zp8Q+^>tqbWbL@T}hsO*bGOr?D8cD(wd}SX+Ln)d0?rFySg2pB;lJ_Ia9@cp(2panN#jc&H1}?-H5$f-s{S<5Vuv_YPPuA`HlM>$H)9$6) zW|XkLT}7>uC4L4)puex2Erbm^+#ZS>rT?Sl@#nbI)~>ofpL`A1bFFrGtY3c)+tZVG z*JabRXbAoB>%EqT^3m+$C{{se4RRvo$32&Jh^D(-g;W+JMGmA2PtVso{?JGHGd=Mv zk6+Xs{k1l04$I0nb#uJB|4d%^W%_0FX3%RgLPI=tiu1;}rvWFwmuOsW-{d$HvwPB* zR^iL_6@kTGp#4Uq*i#98AExEAsgF)&YvwM?!l?jW&5*|@Mz>E;J&K^$E;k>7qZ)Q6>ugd;z)`P{Pa{M|BS#CR}$he zwt3KgXxX&^+nuY)a39TM3}ozP+}xABj2m}x?SQdc(J$(pIFy~N)mUHK$*sP9%`ccU z;qw0_m2pbyPnagx?coso9qLyST93Ru=v*h@EH4=_OHY7$)>@qAgAOF_lo(LjH2SC6 zK3$d2fOh~n<$)n2B~`KOLF{s6J`pv&@!RrAzBCFOGq>JxlV{Ekb^>18rAF(|s));z zDhG^9t{gT$BQaMnUsf$~kWN^Pp1@54fI&Dd<|SEU@|XFlHIsur_$q(uFx#{B4d{gQH#YaD(>B@PPSGH{ zlz+MvuO0n0;nM9X0>QhsVT!Jtjaxo1>*4(k*?+S8CbMl+UaGyFzmVC9{8u>d*1!iE zJO%+rjOe%I#?X{uZ)!mI^p%ICYG4EQ9ehdNgH%47MzR$V9*tJ>;WXKfv8;5wh?AS8 zCGWevxE)+?GS(S zCH1|!Lqsj~Rf-LPjPTfU>62Fqs+M$y-eS8>M%JJCY>m(qabB(6G`afSdIAAWX3a09 z4M-fkE}i(>C*tbya^=W@yh#!=!2_2?yadToh#0=9p6qXqQiboZ_;TIpR{o9Vh|%O8 z-IYsjBTV>g>jYZy{L&wd_b@(1!B*g9&au81Jik$IDn5)n^t0NNTdu85K@IbkxPH@S ze;)sgGr*_cY&LP)-wrE3($FK=1vt9v$3N^*e+)S2beQK5$HMduZ%ReQ$hYAu<41Z4nve)3J0w2* z@?1-gzj){jdna04KAh+yJD*Q2%|Fz+;%Nu@C#B`Womhk%4}LWdDA1} zn!3u3kmn=6mGFQ&vHtBtP3$wgm#S&yQgh!nD0AGYp+k+5S-Z3_xk{_jY3>V6RA6yS zT)D^5W<1ciap%5lyUp~p8@;Wq(rKSo zoQdTTWg|SoteqpeXlLzB8pWh?f(@wBIxbrz9@Vt3W=}^Kz;Mj;OT_w`Ts0#DH$Ea* zZ+Om@Aci_1_}>GwWZqrK^KQ>!ao6+o4-e3ekL(WP?6eHS>!CmEukud&01QR|j@)VT zG2gc@kiQb5pVBe5hM#I&Ou`)0t|li@$WW*avEHfe;bXMIjG77M^R zq@xQ)ev`R2PKU7sb_H=smOsy5;cm9=E8JZ@I;k2&8~!Z4bEoEUGqQ(+<|fEh;y^Q)Lpkqk!)#7(R!!IW>bFXbsHuJNeWAe*g?jI z_yeqkuu%mbrH6AA&SpZiSzOVi=&B3dEgcn*(KK8Ql2J^qrP}eoKAdIiVjc7Du_KD) z+WdJTYaYzOovKEzmq5+@xP$}^Ju)hFC~$mlhaEL(#>A=b{Kn4LJafF>P8?G|{}c7w z0w)NGsG)TJP>E=qLH(bHE+c^NmP3&R4i>#82$V($tL`5E6S2^8+i|M$_V(iRt1oq# zREgzUtA$9h`wERJG%&5_K`PHze+cBW6u7A8m~*GW*(!{{V|DWJjPUo{F@r??F`YrY z>!V#9qLBKOaz)6afMp?9UB@>sX&J1EX}6Q{@fpC6$(NSvOYNggc^lY~A4D`1N8!#n zUU>#kH|FaDToa&s9F?=;gQ=^#U`M*w_&(5&QmmxVa^hPVTnuY19@Uu=R`JTdxhRu^xJ9+5q7-eFHIr zq))TuT76H9*#h45fKQT33T+Jj1MS6PFa96XK4M^i@*C^|RY94@?AL#iF&5e zJgVH>!+trM@8dIL>_H~C$~;v(yc>Q$S;W0YtL%L{!_>JM)(;sxl%7mEpj67eG@#G< z$d%=*@A5ts57>W%E^g`jm#2OYBxwJaz|i3mBWQikboSj5Uq(16&I7IsPK>)#-Y5FN z9;Y(E(ldr9Udcs%lD#nBbM$=#CZqk%;UjFu7 zgwm!8uW=Qwm71a*&$?&Jp`x$risn#HvQ_&#nL%=6Kz=@P;`&W}1dwBkLQG zn@YC3mlRWiM!xSmKC6x1O+g>TO98OWwOSUI z6`)QG7$&hK%>AYLaJU5@(c;BmFNOfKgQC*bKQZqRB>?;e*dH7ulDN(}u7@=Z<|d8LM`{H<(SKb51mod4ou*IW zxwV042vq{ZFVE{X>pA|t_GgRb9H18t)?x;{vaY6auc-3C2mngzcNwQ#?PLHxa(jy+ z(q%SoK())UqLeoCuE2d0F8Y)~b*o}doppmVK0xpSA+o+Z*AHe9=~CUl+(T~ z%0*6rShLkue3eFJT{xVf9x{k_{9BV&3%6=-j37+|N`Hs>*ST-!WRA+=dBG!J%*XZiPG9`u2CuX)XJYqA z2eYW_XG%3@N#PCDbz}Vk0qI0dnw(ivf4gZm-3Ro@e^}a<%TbB-5+DW)iX8o`>GbpF z!@xF=l_Ye9bO6$p^iE%27AaS=nORv^`4o?3@iXTSD8&+`SweKd7b|5ajjesJRdO#Z zmTNMz+7w-sK$1INbfHEco+?Rme2VF28Pf@+zgv`ukOqxr z^`{SYrh`=Cou{Ubo@>5mB0%Vfn$y|0?V}>hJ3ctnXW68QcTsMhS-Oj(TmtADlc1d= z_k=kUm9DI`0E!Uj<5`0U+;?Pw4MKE{+}6Lb=-vZ-HBlkEASI4Kk4!WAJ)7&YiBson z3}DC_j1hm3>s;mq3@IQkF$Z?nr&HC_%L6tO5#~`wA7vocx&Fls;EWfg1pKJP3j;w6 zV2dj|p95!JF7!=T^$G?C4j1HhvBSBl9p!guG1_#$bN8gPbIH=%48wW7S3*VsTPfQ9 zTy17ipauCaT3gu%Ad$1VQ#n^Om=FyxB$(I8A}Xm^YOVUT$}}a19|x;na>bE95U?PN)i6x$-PaK!;aguWy~3-~BNOtS7aX4Mob zRgC-C_wP);?M-O)zOOYDD22QmuhW#niMjngX-(;Hs!*@1}fI9^_y_iTG zrtXJutov=pt=6(bn-foB>(w350v93-E+hz#!i4n)!cqrEu2<8AS9X{EWYAt{4@ND|K*clkFCn%ssqihzf0dP&h zO}!oZ>JLFXYi6X*c_p;t)i7p&J>fHuPh)Pk+fXT9SD z5GYWs7BFz~b>`3NxZtG`n)b(X_4igd$_xGuqW9?jP)P5L*DE7fe?a8Q^gQh>%pLQa z?)eGPh$%~LM~0yRiNiz=%Wg6T;BSwLL@jg z^{@T0Z(p(NYM0dz=HH&7*IK( zAKHrBI-c<$&Mv%z^ybnK2UBmUGmreKTEA_>)f)Z#+E`ZJTr(rI|74d^Jj%iGYyXjt z^)Ja`VKrteG@IHnf#IB35L*|xU7NMHm;+9`BlP?nj1U5v%2|ArHabO|aReu;9khT8 zPXv=PEXSTw4X!@g%7`27jPZ}_*VCUh=T3nVOX4emB-6Wb1CCY*)wd!q zvt6&)%*AWr6dm7?(^5r~l1G87W&EoZeE^#!3s$NR5B<)+r_1~_frc6DxHND6y&=he zG=4yM4+#ne;^M>O77NjsjA_~4%=M7Lje0MWL^sAhnI%JKGy&>RZt8DW>ujXesLUew z9Q6cWjxPXf9j6k44bDq2@=gK}7x<0NKWDdmwFCwn^yL%+Wm3WzO~&Fd{(k*}>ut7a zwnhc-b!w$BCcCu(h?0W|_YtsfskBoTcK-&~x{*RSYFz&r@MW*(=U?LPz!Z~_5yOJY z4U|EYp?i}@MvRLCqka`WUW{<@l%Io>4Yb9onbe_Eg$kPyA-f8qqAG}6ZEUgtuK}&C z>g&^2Ot`HZM{n=U?D%h&orJN+2~Y1=ob)|rP3sRmw_{wF7!N_eMB#s3ceTm(N3JSb z7eF9))~ts-J%ufw9U543jyFezO-z1YD$&zhp{J+c?hGYKS_1o|JncQ=4g2{&yF`S) ztvj80cT9h=4taQl>(l`kbE2_TS748yoCnl``Own?peJPK-F0y|nXx10>@ zT|s4LS{@!OH^5o2_!XLipT!=q+9L63Ts)o{c%4={B}p0MzB1k0arbrq6dY)qglpNTy=PRD+tvmO0R%;gh$tn11!)3`3eqHq(wp=SO7BtvQbTOm=uJeL2uSa}1h<6} zdP(R(A@m|G5JHms#&gczTithj=lgf>7~da8VwUS&?^<&{^O?&SB)E%^eAKbqoSFf}G$41GF>*!`;#hn?IwBQieh)({?Ack!% z>R=>!KvO(UaVn^BqTplCJoYhG4s*R1TDIP9u{_>@{Py;Y3~9t=^C2Hm>-qB|*jFy9 z?7A<5Bny+0VVQtM=&Iux@gr<(XJ{a?DDx7fEqz zf)Ti7S{zDXQP_h3x4GrWkpjU_U>$A9*>Oy>;UBL1pO5D@+J_*4i@Da$%Z+9EN5Wu(8CM~9s}Fi}Fq$X%j?h7a9zF9J zO+@%mgZiDd#0_;d+jJGpjh!K%Kg$Lw-*`ytE)pT(Of=V&qPqcMvGiGV@HC`hY}yO! zBqfge`aOSUo+24|*DbGt<G?U_V_!>INs%)fL@(~=t6nbGZuBgs19Qu4xXo!5Rd-L&G-M}Vd;z48Zv z-=C#@6Z%ECWTtJdBYorZ@yUy5aub2tWvEAiQEG3s+N56Ek1zO}3s~gfRx)}@D=n<+ zc1hFHYc6Tb-h;<49A?{(`HXc4)(5r`W|Kc^pYEflmBd70Qfe!k0mAoyrsz8A*rf5j zaIn{CpaVRr0zXP zFkJ_wYW5E_tg^|p%gR}mNPhD-QTR7bB59v6fzNDJ+f5A+wm8)4d)kQoaXC{sdcMw* zvBO@C!>n_z>I zy$+Vi?H4ePEUom}=2b;K%xs4LtP^%+P}gO+4a6upI45TDX=<(3?>n4O-z!*XjP$4_E1wAr z^!dZP&E65?8rs@Q@);ApY#FYEd}xedMC?FicHazQu_p8FrKRMD6Ppok0_;bsyE>(Q zv-lfhO+t3Uo=gPkM_Fw#k{2@S557Tl&o80<}e_=!6W1 z*1aF5yp~7~+&EE7+!(-C-0QzPYPYg>SBYAhxXAqM^W6&M)30mVqZw`u<#6d0! zJ*LC^bbOxA>#aTDV7CS1z0(S&xt%h^Y4&#qdx(;Uol7*uo3%E#wsPv~)LJsU(`{Pq z)2_FI*n1Fr5k7plY-E~IP%B_GZ>=(U!f$!iN0;X6si8_LHhrpHbMJ1+6nV2q}F_0Voq%Nhdo% zg5s)$S>RJ=0AtdOO?{jGt4we6Q!j_q#!`)G1b)3&7~$jN^XdAyX37WZqyUR~b`ExT zBc2b`7O`c&RUvmd@|pg(fBae1|E>A^MF66O;tH`}egcADaN$2+o!R&ynH>+V{$p3a zbnt&Y|7X@H6yg7$vjNe`Xuj-u^zSbMFg**}ISI=FF*NoqAk@3G=cD)r3q7CXN=-to z3_0MuVh{p48^J3A&;hyInYGn0x!4D9kBzv1Xc<;1O{ryW+Dh$HuWk>PNzGw!1 z{u_ig1c;Wk-54NU?!>uzFG@rBlu#=>P4k9DcF(W+@h?j8vs>V&7fuCdeh>@(`&0gU z-cO%^t|8$NuLIP3z(e?_&VK$kFgqaq4{hyLe|m$Tdi?XDnbJpC`TMvLSC?M?hZX(Y z*-y`d&;wR&f&bL#Z^rr`9<>k&%;feLuSnUmKfn4vKA=yD8bmA0pyL0xN&n{|1CI&< zs;Pti7gNoj_YmJfsp9?Xqsxxdb&3|)Pd?FRi*5_}W)$S#pQWevij;W*t#xKabM`t4JoW|eSUG_DkVV3+eY^aiko*Eg>E9RIE3J`>st*KFITspI0~ zo1;DMmcJ^pKs=*@w`@kgCO|UNKvl3aRj}2Awi{Bk`>&{0`BrWC2!h?K+xMt*Pc3}4 zVIBUpsyY>Xg{>@>rb6agYl}xC*_~JEbR+4rZ!N=>!+oTF(p93HEfdfrlbB6JmcTY9 zJOUVjm7T;;q5ms6V59|B7Gy8}dtv(D-Yp#R|Mjl>@SxHzqA3@bU7F2Bfirzbx}~Kh zpnha^oZ7h{e3~Ay1~74Bad%)fjD4gXB8(bvn8QXHB`4r4ADHJ7{}K!%-s3z=z@t*UivkomY{FziLXB~`QW{4wV61!}3z1>3^*>wmMs+L(%pi$B?XCAmaxhs&OrE!fXsj^4j zN=GK}i~oAX+qXA(L!5Jg({SnlDJK_iVtm($o3#jxFrseLM+V?X6B{Q)jXUX1hM7bu zu9@Pe0wl(k-ndUF5t1V@vbz(h27HOKQ)duj{+M{W)a85k8!dt_yO>$BbWoBt)S>x(cbHN z^`F2YlK@~kScHDHGPR4~T+GPo{PGJHJ$8pYFd-m$_g;R;{K`iMM8{bxI-5|p{E3a} z<%;|GLn*Q@@BBCUByQ0Ku8mwSy^t`zmMUh^>4tq<$?S$^qI*a^>f6PNM`U!zNLb%X zeG|O-LGnE;w#eM%G@^c0CJ^@3HKmNeiYK9?W4yUqZ9A5n+N?wD$`d%S$XXqRn^6kx z59Dy$nvAokMjL>_o>t|RHxB#0lYJ_&wc~r9AVWxg@KQPOP5Lx!{-Jv%0-O z#y-AHZHkeRkv_;`Sdxra7Ze{QyU8Fkvt8N=)DD<6F|k?p{EF#C2xWv+n3o*j9l$L) z$<|??BPH+Q%<_APP`}3ax^7|rf(dE#ow^vKfPHF6~f_vSZ?m&2( zHstH4w|m>M2x4-A2}=D6M>4D{M7brn=d0w_oR@2!qaF#@#}lxDEtEo*dZuG#H`j@i zXzV15Nk`K2F@KXH8+G{0;rv7HSE#v-rs)wurnuCq7mYoBV2^1;NxE<~G_pj#(8j?i zZ36lcJu8U0u2M>PS!CLLtH6!ha=JCrB>lqf0S0Yfc}S!Q|KYhm8vc@D#U0-47Zl#m z5QJYR<5T2!hAuo?gyreT?Xag@^VSVm#1Ki*n_ZRUq~47CZ&sRj+3{_P>v3)5op+CX zKUOT4SjXrfD)c$8x}w1auG-MZBJ*l<&j!2{r72>?bGw~Zr^_noPAShX52CaA52qaH_ zulqEko!V?GuxM+J&9xKO+6p2s3?(Et04N(uj;|@vZ#H^A?{Cp3E{qu zJPSI6flI+1*}b9sQYZc(1+R+qwp6ICHGOsG94YM!UEAB?T)cgh?b)LuR6qR_H!~CB z^e3EUl01W7N^8E2f*B>;JUm+3ae(L3h>GI5=rI=SLz3=IY-*AZT*R7S`;=Ld?4r`= zmbT|oeX)rqPHwHO8$PI%yp9(VEf);(*2FM-`=c2JJ%iB-Dm}z6GWr%4qE2bPB~91o znWXhUp*JrInlw_*iXsdk2zSW;6oM+ z7h;QRr@TKN^bQCg`Vt8iHNd4`=yx8Xqc-Ia&*0+DCN23VQ3uEiclcc=tb4k-L<3Ih zBpn@{Bv1vxtT{;AZFGO-`WWImVc5q`!89CK>J%bV1yh&_&g2*sfDn?cFwy}pAWnw? zGjA4`@bG8tjo~O`-jmQuU8$LGYP2kxA#>ZvsHrkNRl5_GK2ds>ft};gAl}1aV>8kR zgP2OPAYn|b=!pOxYDKeMw6`rU8aD-!)u$rsT@DY()l}X!puJ#Kul~i2Dm?N`S(vYZ zng7y$_pqe9-m?VuTj$;!j}uF*zB|ZfT9ME403ObG@7jK!_uY(W6L&v22p?-HGs8%z z4>?fEl%xB$Q713IbsK#eHsZt_3zXePUYCt*#bZcOA$9k%xYb*8ao08;&0rsF^}1CS zNYSafxd5fxJa>T_k=RxmT);9iSt0=W^<%RgDbNjF;#+8(7s)N%*$UTIk_t<2X?jOo zMqNjqla(nl75$4 zbGs_T3YRLczZMiyB4=sBmzZI>`^uD_^!%#DIYmX2h#d*G)LPsnFb<Y+MZ1Ey1%Me&R;Cdgu~W&V0Vb;t4DA+qN!`#<(JHV?KW+TBWf zW6?G+<(5Fw=gqcMPiWx&?(%%@xmR0XQa3AfZuLoOkGfUdJaepmd5tie5ioTYFwrp+ zAxICdYsce=xU{hgjo?&#yWM80n^#OhfoqDq%iYSE(JFvvXk(;y$0({gDS%jA9w2#) zM`SbjJpq`8laT%)(5NsFH2AmQ^fxa6l6+Vz<)S5y=km?9kZo-la+NZ6^FNN_zugE!sP~#0*0;iB7Q*$6RbQJS?!Mx_fu*W=o3;xgQv$VRX? z*j$Fa&%3%Y6>%W}2Nf@(68VVx$7I$fQgg~LiRXoe*^FwIdQsC*N8Wqo-EZT^{-_KI z+hIZUZ4S7f?ehRBlKNpC!)Qy^&CkmO-O2zhPGi_DSIxE7-uqs0hjpyTuKoaKJGhAuMgX)qKnZ&Io7xHUzfR-j-^)_K}w+TN+TjO<;`6m`h1yAGeGj0pHqg2;j`1pnrc%W&^5$RUQ zY)s9l?<|IOjVKINW7sx?$9=n7u4^Hzh~XB?E}j`b!d&LNzq^h`**FMRB(5WOd@j5DTyL`NGsrsILX$UVFgn!0;^&$8Gb-?tH(#MaVc+`= z^=AKbYrt>fhwxh<0@t|zxwF5!^!$Z@&)Hs3nwkm>?tkhV2)2M|D|S+UbIE`}kLnQs z3xPnhLWQT5k#Hl-m;ZD)#2-+JXKrK{a>X)6A>c*$4=;HGLrV*kmdX>A_)`7%q5S9h zHDVX-`+#f3!s0*8Y2l7Qq%0701LTQ(dz@RqZxV>k{a@FAP0jyJ$X|2ukcLVBA5#Tpu|L@=6AV<#{pGw&VZ<^ zdpAlBkg8ui(*p4C1;yp-PrhXYq?!!q#>Pp|rCY*B;nwE>8!540@m}Ol^`+0bW;v(-afsj{U#U@3Vo03rQ{2%dr`KuF&xGkX=IQ+)LC**;~u zS9dcb!Y&yy{*Pt`Fqu6RK)-xd_`hn*D*}v^g?RVZQrz!+b+iAb@wOfS zLO}~BmH*Mc{%UgkbeJN#FT zl_`mhESsalzonV~L1Z#7Q(9|jcm7w6BPbabWyGn1$3J2FKZ7*H>By{~DLK22jos2JtrU`&0n9DBy%gy-1LT`QfP5j;@g_*aD~6- z#Oge2EuY)#8~J>pQ>Z+`B&y;@p2EqYQg%~;;$=avCjIgzbtt={J`BUxu z$E=}ikC!`-OTvt_=1O07#6i8ynq|bX)ra%*bv5j|3e` zqq<_Bo%OL-Gt=X{7K~rPS1MerX{BNNQ>7uhhtKUqqFQX=o;rQ>jBq2ZOal7&&6Szh6`&WZblvs#5D&6 zk{+rsF+bJJW?zoC3SEimihai<7V~tmol5p)_|Bfi^F#K&p=!p)bM5rYJLT&)Ci5ab zR@{0>d&6gFwv6Ypz?tkrhX%75?tfqia^Pi^yFvPMwX9CZ?C;ElJ#=6uC5+}{Qg+%e zpjp_}&{>xyZc-Ekk_0SwOpc9wZRD?$8O2bSMXr801-nPseDHn(uV37>cPE?nOitX( zh;y99sM;2hbF8(NTw0mBev`^pSZL)@FZ;n2I{-QbH>%ez_R9f9_$^b z0a)R8qpO+x<|xJ}{d+1c1r#QTP`uk($e8UjV{mnI>NTi4sgy{JWOFUL_kqeg)Q8(w z*NdoHmm=U$BrUh%;k=8WwM*o(%ye)@B`3`;3kn-NNkj%ji6$tw1;arp2Hf@u?G*U$ zTLp>E8FdmWk=XdpS1bGji`^}4B$#BoH9{XsxhI_oWb5dSg)48`@4bI|6l`#gz@sl> zCwL;L4gUgIfNd&jg2P-ur&pou^4r@fRt92TXjJEzsU?i@{FS}S3rS+VFI=;#8+>0# zMPxX$Dmii`rHT^R+1s-o-;%tE9@hv<8?!*vbly<}h7!{aK#KiVZEy3h8O=UsYOf)x z_0t<;n{}3#D~;2wV)C-7*wrptX9<|rw)AktED>B)V?7+pBbqOXYQ;xhu>Am%nrIC! z)Wjah#rbqS62WK0AF+2%k!~%vkq!XCC_?YaX8OJJP#`f&*M4ecN8bVr&CWFyG;W^F zMSpBqm8~k^&Z#+XlH^sCgx$Kyb)|O_#wsjwq+?e3P{*aqhdPFO6Z{9$?or$}{$XPe znm@)W0$kl@7dtiZrm9E3$44G=FicNlY#gPcGCEO_<43nW&;kfv&8`>QOx6GBJ2P zOKFUmbf{1&YD})&df>Zoa?zHE?%M&@?+<9*^DtRC&5q*H#BTI4(g9hezB8ovlMZ%eUyCFJU6VyX0W4(V(8@5`AZ^i3k@%NdFVTi zu}62L0in@GD0H!v8exFxb@Ir}N%F`-VB~oJurxE{ zrEJpK(UORKbcdTV{QaXU@qlKdGn#0V_I1Q<5&YQh9J+ql?emFaK&XJW=0ahDAdK|n zLTcleNgp>jby%CJ{x}j+z%BMRn|5eXh#D2N-$v8$t#lmjhb)k@y53zzn$f6VlUr}+ zHK6aLGl@#P(}*|e;_zMrIA()}twq|3uisEO!eipx&S8lgLt`a1aj-5itL^w2=bG(! z#U9jF(vpU!6H-iWSkD*~gV6H`N<`a)$SmE&zN;=%W9$0cS z5=;2_ubK4BoN!EkOODFyfdvx#QJaqKnUQ)i({9F4po3MiF|xA3ah#XEt(qCfT9;X_ z;aDn^qd6%IcLjAz+706rV%u{ue)+bDU7Tn5pf?I5+nBU~T(nv`%a&vuLG(8pZzP9UfU1 zh0TU{-8kOhjh@rqw0tZ*91pIP$}hH0!ygtaG#glToL}&N!DFqLkUqPKpU+Do3DH4i zAABh0Mj=%rnS4~p)NJnl$GmrTX^xol+@&ygNakZ;62u@QH~(}u|3%UGQ|S(-skmiY z>FC{c=;#+x2~)mfNzs0d)<^|olnx|D3s4Mm1fn&7dKFW+oCu@bukLvJb!x?Ht-RmHsz8geG}~BH@@jGVySvbW z;YDW4nAY68_;eIr_hf>GUWN@}cOZUA(+U_Rz<;VQ1rnVk+W%0UQ$J@EDse3{P!$0! z&XKG0>)sq`;VN${t(gYQ{^mf#Z9`5VQLCDm?_)@pFBf zC&q@LKw^msw0i8j+#oRr7DY#IiSR5Ad9O^UO)7fkGyhI@fe5wOPdlI%JL}s(#VD>8 zus_~#9zA51*WyV?Bdmp!+Y|*8F&G7tha5#=iae54fORO0aNJA|3;WmmcT;w?u6_8; ze=!rb?X@bln;f{xf^J_i!cSF!_LIU2EIPrRH5jM^E66C?<7Ox*~NYuOVwob z^w@hEl6R_Ot4-~c7@9f{HX&)$wDWm%X{9Y``OzV7{FsDbeFdJ93-dE<9bSoiQ<2$zb6NO_xx>f3&`2v^9|1vQ;cD;I zp3acXahkNI{ZTcL`e8wl>NNpLXl;;8nCrPvU4^nSLm)C31aI9$vY7d@bE-+V0_b8UC}<3+o*G9TeA|EEAib)tCc;Z zu2sL5p>`TixwFw3=p?CqNWr4-s3e-`Gc^=s9@@L4*oyhg_Cxi-0PU%wd3NE43jFQQ zwgWZn8tE$58$UW}6!&yIIDg+6bsphKEcfi4>1+71qrw(h?K3)gJ4II30jh)JOeb5=ft7`m71+`F%t5Abci$YXC-%x0Zri5>6^RO!+Re$N= zrIq^XjxIe<9dSQzku9jkGjEd+D5lpsa27#?Jc$%ecoxC5?f;#!+ub=L{OW1WZms>7 z=)yv5zL>?pxJU83*Zc?<*Q=?KH>YhrYV*Z#LSL7!Eq!~U+FzrW%wag4d&c?Lwdv&H z&AY}Jg+Bs)^lsm-R8B$^6Wo(xnSg2JXQVrbHL3WHs&@ds{S1I*5oJ=<;R+DqPM5h$ zmvEw*j^#J?Q2Xg!$j+hj#f8J8OP;&;BBx#m1bA_>WCedL)>8%K-WtB|kcyq*n-Zkk zI3#_mHQYXpeNl)CSKd$6#E*omVXlj3hXd!t5dHx8^xNORKbqY1+qOi(Xi^` zt;Lnhex^G2uH^Q)HwO8;`rpTHX3uls9&wKqQSF@=oZg zPk8fTH!CBO8g&hZkFynr&xgpZij5|Kk`C zV$C(_fLK$c`RVf#Y_aTX_h#LGtv8}uvf>9-NTE;#++*LyUuVbuNS*Www2d#LpuZ(A z#LE=jbu_JPZNG8*aIwO>>gD=~>ITo=2dh~nL>H_~HFz{@cIw99i>W=sVjkI+3tKYV z%!zI4fD57k8kPt<_e`3@)iykNg^ApiPgC?!@lCpC>!ve3%q43!y81=|E}JPhhB?AAm2i_*XzT49=KeDz`EB`*_vBT+L*_S@bu$DGfew( z7jn~ZY(t`WU^(|2p8Jn$!Qu(W0lFzlyHpZ9 z#DwvZ=2oW%?I~+_R=M_0e2+TBprz|zOOXI~FHg5cfB%7KwDP8p(F`|Q7Yrlt`OR`IWt&Bnn54)1pFTWbsabFyABF}c?hh$7W@cQ- zRscsW$l$3L^FnPns`LuM2T9$`4vy$<<~+HbDdtqYu@xon2*$6pKoaW+d_Q7d<9qfA z)Vcau>kw#h44RGZ#*}2NR$VRbV}y3xTSe>X%`Yd5;`U)A)w_&}$MUPBhBen-zI3rD z8r3|%QW|>YqDV@RyhWw~o7O3T&w_mX_=u?3essv~2eND*mV#|`vBPot226D&7ez-z z|9I}5jWE10#lNo5WSQG_3XA*U*EvVG@`Nsv;tzQRu>-uT$9FXu+SeF~N+uc7`46F1 z1E%R{=-WSh7Kn3;%^b5{Dg$Y2nwSn|ILnKd8X0wBq&(TUjau~`!TNF&cY|RoD@MTO z*So%YFK*M;1c<&nr0s*J-7VouR&rngH*vM@<{GYo3zVpsLzcBtP=A@$g(CQbD z7HqY`mF2vOlii!!X7^gI(@*JTdu16d>P!M?rYRHO4CwdlFzKi-_hL)S35_-^WI=2$^L%`aQ4wCDyUec>Q}=&b5gr!PW_{ zxp`T^y5kZh5cMpnD$DT7sE`O-Eyor9NKLdRKHf`XwO$n&&Igt_ z3k#_lYJM9zBA6&{+nKl^+vn%$Wg;zS7N3@`Awk{%)HmcPKn=?h!<)4;Jd^cR`9_;Y z8NX`*%ONE)Z0x{cEB1!YYiwXl+#@6;(Z6JTPh%TxlxVQLTG$Cmssm- zK1NQ~aTJr+HS)WgJu+k3qO3OC&xR)MXQbyvCU#S0rV(*`$p;5D$DV6K9?}+F(M)Pv zpY`e1cgc#f%mmw|iso$KoO149ZpQ^|-yFrpW#@d=7sJTqyJUSnXXW6Rd?FtWc#K9% zXwM{v(xTYO>zBK*SQY>k`t(fxkje$eyRsNGCqiqlp0ven@h#F zJ;R#J9lImd-`hZ*U$N=VV_;KrWq&Z4O~F-NBSZS+wDEL&27a_1@`0u(5wC8>bA1)e z;}%Cs714>ew+0(;@}`_+3Xq=%(H_auj(H2|>SppBt^m0|qNy!_P3X&wAJ`e^KV}g- z18L<4#JDUGQoyOcPS%AQRZ8P5Nc>gPRGMLscriEVShzPH?_(PVpEW0BcaXv~qnUJ} zow^djv1gCG7@$5R&f1A}-?wv6)sIb8_VLA*C_NN-i!}p_nPq->(_n7)4ot$!YQt?# zt~(Ff6vo4%_}agR1lGS%xPMR907=l{v!w$Trd=P%#8hm*n6LR|WhyLM)8|XXblXF~ zQ;M9${$5)FhM#y35zf*Zjto@~tK-)VHn9&R*q5DYWj`%zUiDcoWRi69Gz5w6l$Yp$ zV|X$@x-ZjW;6X(9zEJ4CUI03Vr^zNNWT)>bPk=;0F;Bt?783=Yl`6gYyAy)uwi?zS?@ROoh-p9O! z%+bT8+MTK1F|_XO$F*Zic}19_5#rATGU`?#VW!VDw$9zQE`|QYo^3jMK-s<`Nvp4|8q~X-n7G)pv#rBK#kv}E(!%kaTe<7D zFGMLT#Ou>NPFB~(;KeQ3ZiwmtLz?UK_Dkka@`RmV!2?XGxtP}wN-b;6-8kD2Ow%wl z*DIhHb0=y}QLZ|gj$++gQ6P`nX^VLWidIwj_FsRtPs8zrcL#(%k8N0=zC`YDrC;k+ zVQwG1UVMmKu0|swp6J>VGP8HrtnQ4+{G_?Cn_^$B>yC@h-J5F66N0K$Hd-8>{i=;Y z<@PR+^D8N$0rR#M_2dixL-!(t8q4GEoL76&SS4yq&ZW9JA z+brakoof@0t1T!bxyC@WXlR?V9LED?M*&sWr2WQdtns#Hj1FWf9eQTAVR~|M{5@#j zr@;Su4wrhj?J`^~T`qlS3p8eoPtzxj7ka)1DM)YTWDEFw@n+*OeT~vGMybsxOK5c4 zr1$aXzQ2jF6slL%_HMKD<#27xkLOzGn+WS1%q*y5ZdgTkCUFR{9(Cq+gO)E)6Q@lP5EV2LbXjdHzTIzUbul@u{K+s-Hc^ALL^1UQTmTM)ua-al1joc@uip zmB4!?VXjhw*rAUf#jj0IMss0BlanX(H@`LRY)+>} z`gFqY$hen2on4H7X2d2}A^xcC|JFuD1bBdJ2GN3PtIW08TL1qdON_(MmE z&`bBlcAiuzd$mk0-;M{;n5}3{W7-N8e%u`BBo9B=DO<0+eSl-LBo6fiS>5kf51J@$ zK;$dpG>X>(0w=^JVE1fO*h#Y?9Vr#6vH^E}AJgt9s%5^@+iVGIn&oM!9kTRJ9qJ7+ z{`Mkic|uf%Ev-@w#1T9_h8@Z`r(d3W?wp(&c}A}|`w+WnYxphkg~x7wjeQL>P41rc zOT)vo4DYZ&pdF5)ohmFDom&@iLmyzQYCIp_N~BYk)W_g#3c%k3JV(!PR~^4z6zL$} zzcLvXAVapS?f({?h&Db#_fY(lZ4608K`&3_6!@7MJc>;(?WJZ*`cQ=>BXilT#N-oF zIYI7imGCtrruRpD146T?^ov&ytkKHxvWP-WluLBz054Nj$WTKl%W|?v*NM@h!6USO zT;X0kCPG(t)KfMZkHm(_#`!Bj&a)iu$jM_zUSwkaKFHfR+j%(RQ|jtC)VX9uO5Vhn zm~Y8JcU-^3A#8&zVm}3?!8Xk>VoAGi^{wL*!ennLMdIKNE%I~8*Fc!f22G7?+!k;+nA4Dgv(;wXSzd4ibhA;PTzv8`qNrLk6xeF`t@$KvU4N>+5p=>n(MZ zi(S40-QAsmYx5rYvgG+V(@LcqjYs1iFzWbZbBwgpk#;_ntO}-yd>-yz$|IbW{R%Nv zWi0~&{08s5hR2kxxo@@BY{f_FBi>L^XG8KwCkwZk>!`(+$mmG(IJGOp z^yka|MV|$IZHA&;Mw8lP_lQj?f$lbyA+Iqdc7YQ@!ubW`(8(PGf>=0z!GF%xQP=QywjZ69>601$~PL^%0gow^R%!!OE1m6vIZiQP3 z7Gy+SZ@*{d9u)0g>TgBa0RYg_a_@NbrHt&zmyAdIc*asPsytR>0t}cW_o~^M#Vf7Z@Ql;MYs3#^Kxu!N`q`Ase+ZYmPBdh=5^D!@3 znt({dl(1V+8%b#TNuf*!m#4*^oj|A$PO% z%4)5CWwd`;BXA+sb(9Roy$U`gV|>riI=L6;5*E2AOJBX z)S;i+hAPysMApV_U3G4av+6AwkNuYfyY6aHmbZrODJl;{TNPOr6~=V5>=^Y;yLgan z;`2wVOkon&TBGRQ6Q~tON5;nB>(Li%&w^-?O){m~zVo?L2fIDq4x>U;QcFNsCk+9K zGa7E-jq983_spOzX13OQ06Tp0-E@f;v|?r-=9$vhPup~x=7^um*~6wWked%_|EQK- zIYQCsktYzNt}q%YI_f>M;>VRl&-`W5K(4M!%0Vl&N6Ks2g<`Go9@83mbgx(71w7tk zQliqq(#g@vidYmv+`|)dM0Iz^iNIw&6qP(#mQi-@x3FuwB>Yj2!KCv{OD=EM+V@pA zRikX^fg5#pgp01ZaREc7cSffd!)4wIQ-*jGfzBs!> zcEe*)cF{`31CX6i^u@(6xulJoS2NS_Qg6edVf9;t6ZMsBSZv%&BNuB0Oi%e%^`*gT zLtl=2$i3%AahF}1XbC5R(Hm{*<+xE@u7qh2&9@+?G!lPiAe*;GNcB1rME8exnqw=r zdjD~b`S`S{g0B}L1Yj?jP`E31jp9~JHN2)+)2*;*(x=V9;}y}rGCE`BmB;9SL|fXr zlNvZ6H7LEt+`68#&0~11Cs*IU^o&9# zrw`j7ym?GZ!Q-Fl%0gy%3zJ3ZWkyq39}QDdfT3Da(F^3MG!ENre^(<~n$5z-C3{UH zeR0`*(<7TrE{ASpg&Pi?n{6P7VAsqd*?8Gv#z^(kc|+cAuZwPtpNiB7yC@_u z_Qa!Ko~~gVS-v$$ZIDkS)ug$B^^J8Zm#apc@Y&{`-aj#8<(6!GUW^KJ_sZAPRF8kHh>}QUy4gV(|!EgdwFHAdOv5)$?ejPAtZ6P=1zIhl@ zFK4o}S+=}u?$6Mu!DuT^%WlN8Ss+y6s;`zjyx>%-FzH9TxK(WF7I3wc?!+U1%q>-B zAt>1TnFDeFUG8$!60DE@o_oG*e*{{6smjf~4DPYaaA=LXl*Jp#LH*@+*J zkOVP>@j0CLFd8_mMhe>R4qbg!JLeWJZc|v0koC1}B_l(^4N4Mp^EikWy)>NS{of7Df!gEFVjIp)h4Be5fYK!mn+{FTpfI#vPnaXE{=Tj^9nzIOyLLW{qmjzdp(-e3skL9i)~^Ck7?aAk29T#k@qOtteUajlDbZQDfb$N zlfJ33nc5QoCipXn73$>~)-T8>OYAQ#vz3aVc`q;Tp{-D!W>n!B7wXhiZ+lZSa#-5( zF&+I*!xZPQCB3S9a~>=-aH#Tqcw5W)8#k!nHFTTXs&UXM0^hEGi>Jk=Ds8*`c-Sq0 zyWWA0NRIqrW`YC@=NVNFj`q8gCdC>1_x}jxsyb@n)hAz5b^B0@lylicOB=7JnM>PB zHeoPhs3y@4YgTXEV%1i1a**%E+I~=!jc#GS;#-I#|HIWv58!AK#dU=&gK+Wjf89&? zABWcLd`ux&!(j|wRUxSg!ikvB$3vG?iwrb+i;L0p<@>C-sgrKloL%nOZ=9buGm!hp zL9Zbs#wI^mfJ>hAaBA%=x*|Knbo_!O&g6# zMbE^ACc3G`@<)&S!2)#i=@MC!Dw=OA^p{9DX$k&T@mE2+!^=lBtv)@V^SPp?gsC4! z8H2p7a#fC(g`!v+?Yz5_zsnx@JW0yYairuIs>Jo!J7cxW=LTcXRg3u!7A**)avjc( zi}dGbTbCBb(@T3MRMN8~@vTehpLZV<@-i{fgzAqgbD!@_%4Z*xR9S%HL{P_auZ;Q> z`R|zse%o&pKN}+q)w%7drz0*N7k`M^C!7Ajju;fsObW6REi}{c7`G-g&yUT&Jn9eo zVTs`=I_J|C0e61e5EN={d!HN=QfBQ`aG1ZzAOAiK58E>+K3MBy>p$5!3l_taWE=E9Z&905p!IZuu1={8_yNP zBjn{H>L^3?yE}qxr{uLB02N_F2OiLP;+s|24Fj19+{&7I!cK-JnwfE5ZS6$KbDZP% zc7tBM0NJ$&UIEeTQ_~)M(Kc3WGdEg9*(PXl_$bSKr{wAiTISX3D3`oO^jaXI)_pQ6 zNG!&2c!wvFt@;i*dKIm!GKcL5coH3>z440sR%U)??o~Obr#kAkE>&{~*C8RaXFXVq zlra0~olSuq%)>6bG0Ljc3~VsJw^P*`z8$1u{g-M1>H4)lfURXeoAUjtaD^4{<(t7x z9G*LW7&WesTN`V0(9P!jKa{-%P@LPcHp~p}gplAaf#4pTgy8OOL4v!x1rma5a0u@1 zHi6&}65J)Y!ytnV{6li?clX&R`>%7W-kO?ut7fKGuU_3xKfT`7@7Pm1n_d=>r?X-! zmL~6}hNx|CHDap|O-BtRJ5qOZnAvC%wHq+;Zir4~u3OP|T~Bz(s=r#&$?I)ksm)?V<3# z3qYi5PS}lb7lRbK&APx})RdsZ^^@Egp3=F_&#B4Yaa4OYtKDx(#zEe3()esw^XH3J zhg{5zZ&bqtZ>QDf-+k+pu4$2x=9kcMQp zn!{6g6s9b<_Kir={j8CPeBVOd$n^D=LrKwu2Tf(WbO8_%*kZdUt*?`o^n*y6D)E_Q z@`bj6A#0^o)%g17R}5&Ob>z$Th`Q~0^OhpWy0SdXpl6tv9Hm~hBzh`T;>~=AL`l=o z5V^^5k?CCvSJyA_#r!wt_0Yq++u`4*+NGjiig>3TnEvNeUe_IH#v) z)J`+>d={K!6|0+B6uYb!!#M3*JJG(;>0~QsE;LD@*h!eUbF+gJMqv$b&#%|8b`1|AuQZ@tb9SOJl(dmaPJBLHfJi?E zy%+FuCnW#SuYw8J^k!D2II54^XkxCsz;w@5=i(Kq@a|m@kgyN{bR%ue)Mw_b6p)R+ zf-wZsZV;2L?Uw%~Bkrah_gMD-{!C)_D7qX;EVc10)%i6~$v2%0zy7&cV*BskAYFF= z&rQ;drz>BcU+rk;9|8`7@*}DETSOhqUL_r-#z>4bzKPttGAaN141xB{_|?U@GNKHc z82E>m?|$i3TyU_8YI1@xKWTu_c01U91Kf4P&ajV<)2bjDFGL@;*DjtXLl|wt}f@Ue)V{HqA>t4y<-XhHoO_YCZb=a2n_6|ec_Zq!&B*Skcs}h zDztC3R25fBW!WKeO3LS?6?a_0$NfctEIvUtslg%5-e8T}j6$6?9apwdLdBkj8nFoG za|hRIzLv*xwD>i*I_epn^G>=qwgC7ggGECn`W)%jCc%Gg5SVGL`c68S&Xccg@X@V? zXHc;`0?SDMxYbe@Z&Cq@l!jkoHg-C_=V`w~56{ycz?{s(wQFA+kzgmN-xR-J5O!Mu z741Few2E`Asjw0aphY)u7*g}RgEnXU`U4ybI-{2cf6D#<-fsMPOhu$yl)kbJQVX)B zq+C@|EWEY>73-GI?hZzI`bX_p-BpoDI_x`cq2VgC(D!;Hxr>_D!!w!0$UN3>lN8*E zXWrO{%t|7od_V0|97^;zP^QY775Mq>e!zAEJi2uba7rPYxc@lUOj?$;7{8ddn7e4* z%eje_^cr?Z)%G5cPqn}zthIrI2oI-<0iKDT!r3wqV5dv#LZ}OFphhM4NZ<(|TFJ{2 zeNFtF_#Fs5J2Bm0ceCJj%E65CMfi~akc8E6tfDgOSUIc}ymq<-gKwn9;)bVAS!xae zh`3t0o7YEN{#_S)W8bjJg@0rWJ?y_W(gnghJDY{L%~@Oj^sRd-J=fP@TWl*=Yf@}$ zYwJ?-Sm0-SHTiTK(djz>x$S;@mEFydAgNHc6a?ydpu=bVgQ~HvDnxK-&7UheYk9C5 zyib%=M%Iv75!PwvD(!1y1=NKkyG1=v*;#FK*Sfzw7{SW`fB3)WF|ggg?WRx0I;wK% z!ZtGMuX*;c+H&l%b;~ilTSea3cU#4Pgjl%-kxLu#OF(#6z(L9dC}H#wi%|=*=~ELX8S`9-lRY^+=L~0M`m-Nrsgh^RE2Y z4w_lm){?rQ#a!Q>JE6C$(<98(0U$R7Cv3h~Z|vI_CX6c&!qA^qHT3Z>9rzq3lb)EoZRR{svqZ7gz>7h~~da!5H}f zWo>AUY~OK<`SG`v$=wxaK$311{2Ox)BI>yf;5X{oE_f|&xUMoTr=Cw*=$efqUS?b$ z3NG4bTz@gu*w;g08_7N<`OHjBk;1v@K zD&;w~Uq`^aRdZJ z^ziVY_rUOgqHM00;b&Aq6jbf*dG0$xc`^2PM>Tz$PVNjeIQ{;|HLaDM%ZKd(_M0OF z@PYJ`KHHY4+PCd_cGoR=CDSj(F!7SBxH)Nle)T&+ZQoT|*t2@88DqRyi>FiaWvtir zgiD8A2%!LXndFM5O2QYlYzgFmoxv&Fg>_q+(qfdGzO!shlC1%-kT*<4rUr0M5&ukP zOhZs(wc9<#erUV>E&5zTHe(3-S<5wJ|;aG zt4~2z*Vi{-TIo{jNlf)AT?qtwdwiesHakv?D)7E*g#~?L>dsi2TeLx^-L-TsBC{A^ z!ZC`ogn4g{zbhVY(+q#wbN7|L?p6ejxxjuX>znFSKRD^BVIk=PYAVN&)DhK_h4i?#LBFu!LLq^1!Yv~HP z4qUtyYE3b!jj!*BomSLBY+m?^btzgi@)z|Rkq-c|;jhuTUg6Swb%yIbs$6SYC1ozp79XmSmC2^xx+wBDK|jH35Bz!jzFF04lwa&PLkDMAO0MAk{ys6Ji*%V zUQ&?>)|W8`7xi^Hk@@abP*C3MRIVE6uDXd+$ZJOOaJM}oNuS7|&tyx$s3Nf6;m$bN zX{inDy4qE9Fqmi|tT#9zR{#I8#zSLu7%g}*F8nyDy@{ld_??dRx;?^gY>)}>&yU!)xkO!*Rs+% zIGgmgN*>mliUOdB0A!p<{GQ0U-dhYGb>78+B8u{aNv5PM?1d1zMRC=LGtJNMUW-@D z2HluyPqPBjWEd2o9x~Aw*zw0gx*ON~47iRCl{=ic=%p1#!Y#r}E-&E30Nf`;Wb7<5 zqYcEgv&N?tJB0GdG}ThgN1j9S6)xxSC)-Zp*5C<{v!M%f*w?k!Zcl46UT6%Yp?-?i zk2$hiy5JCe1{+XdfH<|D|8A6s zVTmXo4(?Aa_s?_Asc5eWkthgDF(Xi7lC=$k6*6O_w??IN0z}(oNM?zjS_$HN#lS^9H90h#B{Qq8JVbs&1!m?3**H-c2PHAF~3ef~0COU4!m2Hi&U~ghyu9&?k zs*H`=P7!OYOdI=j)q0AL0L4f@Tn(GyvjiY^ljN$!AO~a6%20##`_-O5)(>!jxJz(C z5xS(D!-&`_?K*+ryo+`kN~JeDcWkm-u8dX3-ZivJ zQ}aEhv)}{I6F&E|RmPXQp#l@E?Oe~j^tTe0xQs{Gg%7HkVAeKB8&fjC=;W%0J?W>< z9tzMxd87eidf(Ff;%jW^2D-Opb)%uC+{wF_0Jj_6&vV{kl!1;@r1`6o1yf-}viF>; zz=+rYy3$6tT-uK%_7P|iK?-}stU#qtd|s{-)2DUCwDOHu%G@5mX8H)h4hgeKyCUhZ zr81|ob2O_nrO2?NYL1|%J+qsBKySW-#GvoG&~t~>@T=XFtB_d6%wTp@Qftme#XQEN zbotq9ZcB?%E6r$bPIJ8BZuNzc>3WRHhqywK|7d0I)7asQE!hW8__vD(s3iQ-GMe0H z*)CB6*v%>>)s)p+s7{LqG+1W-Y|`_I-EBB0`5WS!&8Nxac}Gc}PU)~;kX8V(n%GG% z))ZbMY%3P-GJ}w1Zs2^2kq@MV!m8n-I@pLrGs)~n_$>ZYWq%|R$NIjQH|J8Di5~`77sVQeUg6@$ zn?p1+cNDBLY<3aL>Po=V{NH^vUq2It58M{?5dXU>VB=zlf?K!~i_K)r&GG@8dYxd( z<>pjy|M*(pA?vWM#cm$ap7o8g@Ef9Y7Z?45B5@3XcM$a$5I<>-HU}jF6;w?!pArU$ z;7s7MDD+B?;2wv9jB((4?lI}FRW11w?o;1JAle`Su}t6ykhkshM#2C=sD?%nXh!ra zMaU+AF!rFN$dYKmtRnv2e$hRo_U{U}jBItHK1ZxWF!%G7CWezu$!lZiZ3LVPEk{u9 z9$y4f*%^T(H(Ef@azgc>I0HC0(Um#a@AF{FwU@8h{43HuejR9$V+vP!n4`%p-Y>Tj z*z_nnP;}u%HQpb(ku8iPFbR3pqP+yGC9j?bYP2KywNp(j_YCr`%P{dW$sKOUIbZ`3M1QSrjBO@7mJRCT+trpp zASfCL;{_&sAZoUR5BFCcb#zI$2FM3f2IUHkd@RX(i#turPuSV&|hBJ|KsKq3}(8@t5<7eZk^2TQ?@`chAK{?|#ueRzr zGz5S3kD`eP@I|a-UFb#tqSq&7>yW=k z$bUpmfKXH;f*T4fcC2XeqQ5;S=>_z1!U-Z3RR@?U1^BGJgmW!(ReNIi&8;qzS|1a| zp(MzrjiUD{g|C(LczI!-)mb)mtp9^UTX~8q`XS1!S3QNS=;OB-D)Bsn&0+It;ir_= zwp}cK>8;)=g7oLy%eeHnlp0qS25?-9R-*>^X5~C2^*@5b8{S>)7H`2lXaUwNeP-x5 z{g)+Ec;29_Lw}*oe}RVI(BT0+z#}kG2g>c1-xq!(%)f>b*-@~yBV1p-&#I)R+b1PR z+QBt|XG4lWH0hf0n_@Sgs2+QExu%wmK>}I>y>~YP7KtUpc0e2^i%=O# z$O^A)n+m2IKSB1iU@Wh!Y)cmD5+DT_L~-_vTFR4cK`Ur2MhsQmm7%z#Ep)qo}w#mJTxw-aLH|B z!W$(Q*O@b}}mV|{f>ecHT_)WVmdoUHx zNNq*~M9nq}p*xnjpQrORONZk)bGzRJ^a83yO?iIJ9ZLV4+HrVO5gFn)mKb7uUa9r^ zp%w$<1CLv-t54~!*P=Y+}JKV7}ok-=P5AWK{O}WfS6xJ$c2yy{BFfyYd{wj zBn6tQ#ug|jq8*L^3|>)+e;gb`$2Tn^Lfgk~PRg!MaUInd2Xn0)eiBiSaZ&CLX!&w2 z^j*B{#JH^@6I(AF9QS*k-@yn3y@2E2pI{#5uNMVBV0$~~why~nX&Bp&rM+zC3ElE= zCskUsB-Du5>t)pt`3BWlvSEV!`;{;Yk@L6ci#kmW&fOxxmIXEyrDM&OI>pLN?aiUa zaDzlQfcP$vhd|@7g>q?D#N_dks^U;1IM5Z>^D29x$xV)`GW}^Y(eqeJ%AdI(HB60S zD)fuRgJ6dZhB6(aetUMuH}uhP>Xo}4a6jci%+DwU;_aDh-bn(1H604GH8q#Fo`lY~ zpKO|42K3o0E!l7#&^iQPeU4%SFeeJP3nnu*a`k`uvG#&Zc;c%E!|#xHLJdQ5mnU!j z+T{6v%v~xL;dq|W&#&HXf13Vj_TY6yjnCUp5%+r~O!GOOfnRV5b3`UX^`H7@5i5(# z@C%}^AnWZx4AHYE3)bBA`=aXKOBr&bNj($x+qDx|51L?ck)NAZpso*pTUWx@w<5QK z`hcBisDXZ#;b;cGa8;8os@9-;#B#n2;s7;6OE z`|xNyC40bmt!eC2?OK_@Y>G?!FdbBvIo-D zG|aXemU;v{KMLMh*&(j6aDLA;OZ0p`Iy@wt2;B(%(_Ij;Cwv85*Q6-L&w|8&YOLdU zDMNoKfRF{guFM>HfR-U;D|LdElsn^lIy0G>;uvHLSfX7l%g`fW=Tv8XGh zDeT7+xxDHk30L~yP43EZW8)g|nD`Y0{KVvigA^tbi$utfI+Vpj->~>hz>vt->%XgN=e1A2QyaZn-}>ZGa}wVZ#clWYc^dMs^p&k7J1oEnpgOi-XaP&?muF`gU6RB=s-Lgn}JaYq{lTNkUlM{pzsx>th+y z4|@c-&AL32577le1Cw(hzA-VL>VNF&|G80$z8e|r@Trwhokf!f*K zMt=c@!U6m)$lVUMetcYmEL(`@zC8{9%fMH#a024U8ZwmT#P&mhO&7NZSj|g4KR)MT z)~(HjLFR^Oe+~FOHcSCu!=4WPHq3wfyI-dIzjs8@=!^)Fv{3AN*g&ep6~0;<9yGsx zqsbL$U~^;N?%420i~gsZF@h-HMJYTNkfVbBBKKb%+@IdZCBQsl93Ct216lQ7UV-Z` zVM-lX6L!G+?N|P**O)w5HW8EOG5g{VN!0(U<=?eLgAH5SQe=?8-2YZM*cbw7XkmVi zre~=4KWXBB^Z0*N2!$sOEXM;kVUr;KU*uze%{L8$0naaf`oGeHC~zS7LFawGEA;;; z&0i(^{hmS^6*j%nCpC`$^*q7SF0u!m3m(EtzWf8+|0i*!9|$Aq@Z?BX2K`SH^Y{Cr zU<#Pd*B!}Up#5LuQ+b$Pl{TB-$oxM?{ii!|$PYS?z5yx!T?m-CAfcBlrIgE3_oK+t z78VvkTh42}n4V|V&o*K;qGeydejPM#EdC&2QyK~hzl#gHdI11bdebeHt@H&L$GQx4H$wO@Pa6Thv^^!} zm&UFO-Wq*Y>3&EP7#IlNt5n?>`eHcr!=%sjJLvAiyd_lwkMoq?xQ=K*=Wsf=wybBv+DAI; z)1>kOq5afywfdyCx#rSz0r#a6I&7f@88V-ucc({xrFTp`~iv!{Q8iZ>B zOxhen69bRe4>A|?=ZQ1SE`}}Rg!TF3-FMF{Rk?Gli`B8MO^2owr4n~t*GDqj*N0PT z*+WAXZ&x%sja37AKe4f98g+G3yBM72yA;Qb6&b_l-b#`m=9x!YncH=TVa5s{o%4wM zjgOMRk|R*NbDpQ0>@tN4mF>o_P`IqL3|GvbwknS95zs{to9rIV z)z25Nh`3z7e}2F((;M++ET>zw#`9F;>N;&Tj@H@SbM2UpieehvRk*DHyTDRI}q2ik@9wKC)JzM+Lw7m{J&r+DqSgRfLWbT}?^|oYDjiQ(uWh|KVn~1E zS|~VGo1IPZQVajA({WKo|5@6>-~1LvYW#xrhN_DNSdeyvhGK* zYQ-u8_XZw4b>6F8h$&p2B_tw#uX>|N5B5*9iQb%fls207$9yRpV549x$|NGXJPzh}|`+|i+Ylye+^&o3} zfz+A=W`uj8SQ@nlcI5tXR4hWXD6*( z)$*i_ueQ4(WkWAWb?Yox+LsTF6ZB?K(KmFpv_2{OeR%;FUU=pFmLPfA;eN6HMwfjK z6B9F0Q@}M|)4=P?I;Ab0nc*hL^nQu;SYK;u3G4^X4#NitsspvDyhh z^(X_@N?ek@DbuT_{Pw8%t!Z~}ByJHU)&uh(Ee0A-1;Ufj7oRTrXR+^pnHy`Fq;|Rj zKRLa-w>+cge&sb8La<%Brxo9eIRbaO{*Af2<4%UmW~`q+s^iwG$Z@pu_Q*dX5Y&YF zIMNYRJ6f0QNA7RoRv|i3zdI#ubx14BdX1dw4pDpawPD&T?S4d(?wNG%&x*)e`D+5^!#s`9iXQPgRk>|qe z-x6hge0?KxrUlx)!=t04CvEDe+bD03*A(f>-}IpU0K*>prg4Bg2C+PF#$7SwFY(GyV`4%M!gZ*1;Z__Snv!UBn(Ovoq~5WxmFln z{RG8b$Kk(uK8HPo&-dhz^2WZWe?*yasDZM{EPVP-CQ`<$#wTRj8FSq7r2B2+9zc4J zDr>w}ZOR$uaF3N3$%#)Z^6F79Q|$5SKV}|$UrI?}*5z4?BdRW{zQGr8Q$`L&rYz}~ z*?L0}z`lZPd<$w*5F=A7)let19v}Q#Cy|>JcQ9Q%9x-H{_az=MH(#8I!o-j-qNUpl zkg^^t$^kX4lp7okz>wFc3;sI)v0DP$$yHcNP2q^@p`%(A-0|w2lX;)WE5M5Hl3L|^#RTBz z_C+L~6Z7zY4EktB;lOBZnKs2NpT_1@l$ZIW>Mlsv4^teTK`T4pw5`JHjVM7<`wb*b za9V^-Eub%;OuI&^LXS`0Z2-3?@)5$Tk(p_2Q@_Ghw5r}NRm>?b4~Ffr=6LJchdN8s z5UR~WUC(f~aqxBT3pjjvavPft*x`-){j~)%E$7s|RVdTtI>I&>2xUh$Y=U#AM+TPJ zIYuQK#Wqtv3+Ed@uD#COB`VkF8#>@1GuUd&lrU%aOfEPq;!CI(ThlPYR2p% zM<#+@bMh^_Vdjrx;B@^m%y}g~n{#D5u~gm~Ok^yusz~RH)IDkQJe4}#lt&Z@Jy?Pp z%a_M5|LAqT+iyQkmy*$k;vC?y?m8(8>>^cyS!Y~mKp{6`yFAH#{F&FDyh%jJ;@+_o z*r+zCcd@hZ2k41kH{Hm{q1t|eVmzIE-RCL(tvzpN&DQ7z!A5+Aw5gn`#<4fn^Ut^T z@O_~wX+V0_LU0a;;o&9uVNjTg!{G1ydXf!tc)8q3x2ui?I$N9u;t|LBzQJ} zHBaW8pDr&!J?meSrAv(io!=|3@0t4})OH61y6ljIgUfXr1thICmG~aFr)vr5tn%C} zG@ll%mrPoJu=Ic_g>$HXb-90P z#2Rt!%Zmc}tkq()aab-L_a2nUsJW3qgk5iY^NAfrr~XYbp4t)3%tnXV9*<H0?V z+I+-~K>156yk6+qd7YPG;BY1={bk%cMht4z^wd`7Fb2oSMc z5At&Ylh4d)Q)nH_tDJ9^g5GI7A^XiM*u}z$VJuLIYW#sa32coNB~&jmdA7SsEx8WM zJ&Eyu$R%q7m>F+5bEdKXP819{h0!dtjy>X}T_+>%#rW{igp89(ld%-A9f$>+}OD z)P=Y8F0p+%KHQhqS7u{)o@)B`A`^8VY?j(46PR-?`rgL_1fBS1R!o$KEy#*m#WGy4~7#SAsF=fUGDCO`j-J)*mZW zEIhnVCZsNzp2X~IT)aKemi6u&m-N%@elOqh?aN2m*sgbZT0f}Fg(64sa5~IZk3Nnx zUHe~D=Y0gX1aK2VkZddU>UFHtDxYSVYbU{~gDeyQ$CM&aK?C9an-913KFwO;P#tb- z80-CsSCQ;Wb-D_Ms3}u8%o09y%JF3CXRkL7`KVvqA0bN8e|pz>pJN@~$@Qa(pByOFc3WrNIiNR=2`&4GIhl@ujId1F_WlI;$^&!}>{!f0?}XV_cZ&pmUh*y7>Cv3~fP0l=Hp39uiC-D}c5AveSb4 z9p!jYYASK zj?$a-$tMIMqJ83S%KLb-5pV}x|N0@`s;O>mg&b=T@*EuUof}JAa{v7r(HVy%2^W(iOImSco3fN{RBe&9)q%cZcx2&!;snu6l ziU|5G7i>ANI|wdQK+tyHlc|PAFVq16Sl0M@mw$_yE4M=Tw$)?Y(bf zOs@QNRIEW1O@o6EJkh@YIYu@F%e!EIZ+an%bOeN!-eLbN@L!S z6h-#EDrw35aW2%b1=NI~IJT1msk9$miMFUQ@Y+1t-aCsiNx?R}(j!!*Ev0FN0yGHq zOtwk{_}mZEX9I~y(q9sB+fejHXR)yyS0`~z?ah4Eov)sZw7-KwHX`kxs0vqDvteXg zki<}YEqe{5F`|x<5v!tj9nKq=W3Cs$-Cl0kf0}eu?!Wn4Ru9+)C%}=4Oc{_#!#FO2p*mI(9w zBkuY0O^0Y0KMxXeqUOW=4_@Y<>H7X5!v!{H;CbWM_WG_c16{w&L%N-#^X z>I;aN^j{|Rk3;|KoPmU1!K8!qN3L_hT;6|sfrw%Bw?}Tb;Qv7lO_DI_R>+!T(~`u-v9_Y_6@7C{`EryPm->x9}C4n4Pk_`)W zlMSMqrkl2sz7|%f_bswljHb1=NI7T37dj0v2yt+z@Vo9XsQRaonrN;3IN4wU`xI7e za+}V$I~~k%tJ>WieKebSPi`{t^3|*5=#%DXpQC2+|K+b?l}(sYGy*tU|0MD6Ik!j9 zsC*X)qO=!}qp|(pE2+`eY!w~B3~N0J?o{jZ+?uae~;fO_d$ZFw?@&lN_VOnlM`M}vT4`)<(dwqir2gydT!EtM>V8b7H2!}K2YHgkr}a4 zAO7@qM4tk7$RabkpCOfShp6pm0hQZ^bzW$k$c(cX^!yG+AR1}mLcadAQC{hsyM$PiX z<6{&CgR=K0ZpY`xMeRvbc@2bQV8iqxl_);uP7TQvuzC#YVR{<|3YjT)l zJdNoK{RnY5Ni(=D5Mddlf%+94YK>$*7wgLomM@6%pc@MhbM%R`l_Z~JFY zxAOH;R0@PWNQ9l_3PwILFIz)}ydl!FDzx&RrM4uHLxF7PO!~=E4W0q&&U@)Q0auA| zJQn(yoTbx@4wXg)%#>2+xynK4Lk5dXjveE?#oC`WbeD8{b;t>|UF=Z(mMOp*+)2m) zL!vy+m{{M8j%4oc-qzoiL4YLp-#ZcCjdq8`X$yD@iS{0r5|DJ1?CA~@|cGBzpC=qk;*$8qI1Qpn2Bd|CF*1o^Dd zS!evgesaGg%g&J>H=fFa+t9khk*@>eF8FuYH6A;L54`V{Le{{wt5r*T)^Q&DtLC4= zWqR!f;m>uen*E?Hp7sk(j$%kYSM_Dz-@=V9_8<8t9n~#owB9GmU?<@F`(ii8FPo2y z-)KlC7*N8BOOU~gb-{n~ zR&mUM#3iP}9^3DqXDAK@`1$6^^q_J0jj3@iT__6EB{C)@H$=nEMKyIJ#&!q74^8-(Akq_lErwy%Jay}qSx+8ERBZvL!8_Pml*CTNF z^SkcIt`6T;9u89_kzCUZcJB^``K=5xiQ=2X`Nj3sFd}rkx?m)KMs-FZitY{~ik|SN zJcDMcGZ$MOZOt37Qj62s3~IcWOyXx4n2Pa%Y)!IP#`gHLBxf%258br43_Vr9)wR21 z-6N=c7xF?ksMI|I-${r|E$Qw&I)UEcb*Rse(c*vxwC?wmAQ4d{%u6W)uRB6QcB_cu z;9F-a95%CS8_)LfQ&=~ecC)^3i=hDU_dD2NCZuwsFvw;U#|mZNCM@>X4ng_d=+CIB z>Cm$z2RPV;j&6km$+73(yACNgY-P#I0a z%W4K=GLG`Mc#C*>SCo5INZKDgn}%ds!_un%haN;#9$=-)nA!Y4#^r}+i@KAT$v9+o z=~}FB`+uxStYRQmS5{Uk(>&+WvH}DaKGeti#3@(zp*k^s&k+mw|}C3M=tC%k?^jI*!v9r{JoPBQn%{M zV6S>wdiq3ViteH>6%&%;`(%9zH?G7yscuunaZOf<;dH)}sptHMVYG^e>2cKFJltv) z&=5eqy0M2yOv{ zZSdUTo+8CHj9*Y9Pccg&RcJ zf{p#ynx~1c9Y?WdSX>KrXUt*6aznSVX;4QF6aEJ-NV7V6SRc|f-^RB@)g|xN)>%(% zd;EOaC&^+~Yyg1C!k!+V^EP8^okk_Ln`3(xsem9u-Xi(KUIC(5*LLT=+Tp9yJpqkN>Yjq9{gbf(#|7wO8x^C0zeIPL|3+08W;RB;?}I zuaDV_@Uy$CEB7GiTP$*-&fm znP%u#%@e1Wi``<`O|H*Sc;PDHGFr}D%&GCdFr0fCHJ>(eSgDoLnDolWNv859)?3kk zZ;)qoOYhd%OSQ`Ecx@>;5}b5yADjAh%i^iGE#9)eYaqSCgg*Mfdvk-ahc zs&e$>FpjIU6wcmHAep{L5^$}M{lw$NZOuzyf+|?aMi&4gMv6dU8pwoh@{Q>oJ;UJK95$p0n}CB4 zWn=qPjUeMGXpPgxyMKy~g5u&OTs5dG8Dbrfj1s~ep!aVuS*uz*YVu_ zAepZbS)!ql9*yc*v3BYgek!UJ^S{s^Mm1p~-~y=9{t^2H!X9aXHA|_zxeG)yGvPuP zgKu(bbekg=@#K&oWCXy@YrpOI>hVbQ_S}C${eG-dLu>X&)J%EgF(AwL%%IN29Ul>W zecIzJd6?yFyl|BNNsad8`Q5DZR-Ft$^2IbNmVsAczK}{DM~=pD`)@J8nie0q^)(#-D0p`^`E;M7nX>F!u8<;AfDq zK&AVh8RMh-@v17eHTm4d$K^eSS~AkbO=9gL;=Kb;B*vqIlHhnbLa2l>Uz6lVSMQ zNn?^K->pLe1Jv)>bMFA4bk9ynbkwTbEcMj$=@n zjhZ(Mo4qn-QL&ScD}e6zC}Iv9yXwV(4UbA-?{)yfe!diU9moKndl?21ynP)A+=xTyxZ31Bz2Z;tY zwsUzIfh0k&UW<=-jkqU?R-K1s1jmWIR;S}fRP-Nt#zH2d0BW=l!^^jmsV^MQrwk(? zxyf5|ssh#RS^GyqineS*eh5Y$HQL<@U+FxS-;hcx1Sv6TMi4q^; z)RgFrUP&k5exO{*?)`WUN?O|Jz6>~zaK4B-!xpP$o_AY_?U-eNlat+Dp_13Y@bfdVBPoC+?XK+(XW!OZis%lax zd-qUfJA+b;h>~!DJHlHrmS+|wmqkCory&C>Z1+%=Jf$xdv_>Jvy=g~MMMQLXj5=C# zbzUH}o--WzT^6qf-Yo3&Y{@;D<`bGJ>NvZ9Q&eWq_=NyFfmwfS)+Hl#5@Ct( zxGhhC)HHJxHNJrV^x)afcz%4?8R6d4sgQJG#T_vWgN!PCzgo7sRIZolv2Z=z94OBb z7$X?PMj?zRi5ChrO7Uw4AXW}lo;B!Cc z`$MTmTrwKhhjOS*Z?_+{VD%r|Gg zkiNLi^xlBRR{skawGzVy6t+rqgM7-TwTUVd2f8B$u02y+ezLYs@z?FEhqgcmeEl-1 zKyUB3bW)gIhf`KrkNV}lj~WVzAG!`(TvS@o2_ikYy!sYC8g=%N6_J$mH@w0y!WY3i zOrlyUu@*5HCtWzESt&&`*L->7F?*^c?7BNqA)c#GJ#~ZPN-R?3zihG76aWbzCTQ%9 zcYor69q@`z7|pdVklkP$2T|$GeE#$e`OrsFfugQsKXsOODV{o4CX?9|TJ9i`vD=qx z^`QC+^+S!Q3J8<1ZBHS~COIxH?w;=l>-$WP&mDVBMtZS4wy75K-D`-0i~fdW@mQls z==jXB*h>e5<>^phCant*E&+j&qC&$~1xcIG^b+0b4m8C3|6}eg!=mig_hAW1DN*SX z=@Jx>22nz!M7lw8=96AOVVBVYOxA%T_Jo|s_kMEZ` zX72l*nHAT%;=Imvt)P^v=JnEyvlW)`6A}WDS3!X>IN6{z!0-Y0g~nRp!y_0|RuAgR z@t6NpFT;;SpYfn}_2(AhJ|wBBcsQ7kLw_C-@39`lp;a=HTdr{WE84NXj?YLOy-5s~ z9u%C>Wlts-+tZ1dckwWbnsg@xup9RQ98BkC@b0dRwxG#-4BgDx27~ZqM0qRy^%dO{ zQiT|&rRr;zo&(p{kDSEtIxoWl#C$9m{;Mx8jmeKU6^-?Ib5E{4U}K-`CPaFxNwLR< z7Gpt?(8hbtZ&By+_`Vmmkh*tL*6-uEI?U@aWY)9fstT_5Toy-;rtS+PD?}51DLzj+ zrpyI5nyjy-~R2+>O|5S)^Y`x{HnTYKmWK2Rv<=;<0NWVv)P+ zk@fA>A~_rS2_)8ZBLvA2#mcIZ$r^ao{3Z&CUy92d!fzfJrwN)Ez#-zRw7inroEB^p zCS?z&;L&9*_w;FJJQ-{0@~sP_mGO!n{PIK*RcHTo{yC|@GK8V0pF?vch27}&HF7Ya z8y2QXBJZ{Etk_go^2{qpydYCUuhvW9=UDc(yx`PNUnY(2sP4ywmM5I2wyaO@Y<<-R zBQnNi2aEaK?}d^Dm%u6%7d~KRD$Uf`G6C$4$D2CqftTLzFCm_-_jn8xj4q6E^Mg}4 zNW-O4x9<}1yXwmNj}>YvXO5ifLFes=2HsaNJYFi$C=T=aU}JG;w(U7kxAWc*`S{Z2 zTFpE-pGd)u*7BF3iO1LQVkTXlRdeKXbLG`2AGYB3kd8C1M)1eYEq|8MFt6zvi(>ry z7y;$t=n^2z7%JKoZ%juPj#>@+SHna2ms!6+M^!HZIZgx(`pCCxyGM_(R2YhafTETi}16zj3b2pYF; z25$@7Y4J(P98Tr(Br0mU^8J=KKdBS2xB&cBiFEP##t%nSUC5weYFq2pFt5VGormJ@ z#ionO(Arz6W#OS|l~>1C!kD{{xnF#INC_4{>C7FnA`K(A_DrFExYn02un&%EYg?Y& ze6o7HS^cKrjkmwCxJ6WY333j^hgS7zMpiZ*d1#ZT;VicwMO6wH8TahFX~XC0zsLc4 z?HK#6Lrn6qx%nSklm7}c2i6NOHmPf~{G|3T-k!!dU?iq{&k8ZzROyc_p z)~bUQ*a5XQS6!^{xy*itg~m($ZkBY;`}0)||I-Kx9kqqIVm^$`OTNd@+Y6$%<@$^ zqA?PzuKJP%BMU5y?LtvBi4zzq0E%#d4@ucQyetoOlhYDcgH5XBg*q5Z!d z}xSuISnv9Xfv4;%M5CUDq{}@;v+uO5!yI)o} zU3)apy>AYQVOWaph)4pxJIESv&kwNjZq@cAhtT6#c?9>wUmM>Uu3%A~E!#;cH;C&S zwN#MR6eXdbf>OpUWHvTeaJ7#z~CLaFy0hORni8#tlpAKZ) z;ItV3TGO22<4H>h+r3Z;J#av^EB8PW#`9a=#e3X?Aa}+H_f&zNBwCm zuCrWK?;@C-HhR^kD{LY;KcM6JTIo$ax;mJ<)_7o=)5bFWNgOOxBl#&-Fz`6tB5bV7 zuYNkok7p%q_jfC!SJ_3;0lqjs<2(Gs&(FFa zCIu0MK)LGM^BTbz81D_2)Lc*n0^s0X+a5ifj@#@prXLqnNKb#*hf`$mA(F$vVaWMi zUfnSbOpmA(UQGjHIuXmFa)V|S`q7Ndv)3{W;6_g&IYQ@6uzh-R{L4L(s1GLZFi7x7 z9cdRZzD08m0u)Z0JFo=_larZ~Jo@Xlj-dOdd`tN#az>Q0>vTP>D8%9@;u5YS-m1F0&$cw^>_lad@NxUTa2yb z;X*B@_pdae5#N-KTAOwE06M}<1}<*0CfXWDRO)7RNeh}_1SYUDuG-;!pyeQkb*+R#- ziTAiudZf}~;Q!$z+}!$XhA++UrC?YlyJ+~*vz>YH7Iwb^yDX2)89uwO>b2(#;#nCG zW;9JAHTp`#c5)tlfSsIu1fLEQCnhIH`Wu*Y_o!xe7B>AI-VpV#Q=gE^+g-~sK(eaM zKDTk?FjW@{uWL}eKk1xwXG^#Ju9t3oo~bx*Q3@7&sF?evjRn3A(`EaW?bre*pY0ij zS8`>iadiG;-ugo0YL5{0)1Q$c7ROEod$MI;l}nbWshOslv_R8S7as`ymD=|90OtSg zI$`1sXaL+f#@C|n5}s1f{voSpUrA;Pl| z@{6xC>#=^5pzpe7BxE7$_n>cM=NWXSyJg$H99q2Iz?k#^(jwOV!963ZT-fv^+`&1K z#cc~#iDBz_L`39Xf(1t7WLivRCx)KJa#QK!f19N?Wj`P{WHu;OyxSxRY+HRo zw|ksZKbXdnaBw$>u78TD*}jK2nDDr%!4={zyl2<8+NqDC5WLf_8}00vQoTMz)q(we z73$gQ437PbmyrMKUOj4W`b5z8fO1`J{`sfKjHM}4B9VOmcfVXfnOX}nivVh`%>IQk zqfv1`^02}Ls@G`u;VVG%TPUI@uR>PU`tgc$PLuP7w3fTZPWD{6vt)XX$YQ(sM_!q}_>B=Nh;a!l~8eB!Zu{GT%2H z+|1*-KA*|p84=)V-^sEAWIt(Qu`-Mh1vr>3BUd|;m0DO*1V0UF~>Y>pQyIaOs3@wq$2JqKwr>tFDWP`aWd z5&(*fF!te{vFZ-krT&SB{R7ZkQUm~|;`YZ15?azJDLy_(@%oL#I9lv|c8`8c+rD@hd+F*^kTZnbit$c{fl; z=Wvvo>V*061k`1#;>9%#`q?aC=BA;*Sa-6gH9=gD8U9dM@-ED4Y4pLsycqVYB=$T= z++<-~kohDMz+99hi3~Bd_daT%VI|((YT54&%;bXYN*$+k1COT;bY#3h3`x+8R>nYH z6s)G&5iK)ai!yF^un{j03G)Np+iGv8dQC}DGQEo}c3oQ8Mk7PzJ}2dR>UTshlhLJ= zlKynE@iC*cWDByJ^)d0Ya`U+&u|cLO=a>T~Vt<3%nPjA=x4JRbhcZ&!X&l2O*tHuI z2h$+G{XAvUL&AtDW+Us5#bo!$?hsUP`47iiu)4|E(e@|uGPzP*GMSp1S`t^U^SDu4 znn+TOMPfgfOjKhI-g9GXWYaC1Cm_Ya+$QS=z@va=-FBmSSd%#fw^BmtG{9k%cK#OW zDMmNOnUM)HZ=rCm>EiVQYWkvJ|NdEK9e}{7cIEz9zD^uN&d=VCd@b5i-9fnwz)RGY z4^s>v9?Gp!#Iu+QIbQf`VJNu~d}5u!8X*1bgd)mioV@Lfm@srku?3SZIonD~J;%jq}Jg#B-Z zqN;8Xt{$SayBT}w%kDHp)yR8sx?a0c>6KB__uW^%{_G?YQ*E8(DkY}N;R*;h8qtkW zFTqa-ud_r{1KoHzJSK|{Y##-GNQn5b?Nm`r=4=LF9*b%B%R%gy*`H-DP1(6_@?oR5 zk6W;CS7TWM=Uf;LsMQGA?}XezP+ux6+p;AOX0-Mq>bhe06BCu&+^<3e${cpKx^D~j zc3SN?0ag8flL-VI<7rwl>3Pm}x{Fti`{ZZ{Z%!iVmN6yks&)?zC;2@sQa#r3@AU=( zWPfWyZ*=bUU2o<$0>y-AdPg?_EH3q?fA-lu>G7` zxPbX4vz#VmV`6aHS_0q0A-dKw@}46jz6Jk8x;&>@5ee}q-8z(u_56X_E~orZ3ZmMk zUc_#+`Qq40V}Gxf68Z<;l?(3g^zf@t_uoW!sp*!&Lvkqjip6=|trWd*8fZ8rxP$Udtl zSet@C*}C_$)vrX$rY~sUCPQ(E`MdG!RV5%@$NLNR^ObWUQ<+(mSg%Yl*ChSMtk1oF zzT+s^`}FVwLHEqv=QM5stKL)x9nibi9ja?6eRs|N9!(?fYu%fG`Ft6=diUER8!!s% zchrC9ZWkZ^`p95$3?*lIgKqu?mE3i2$2?z&3s3JC^HKF1Q+lcA9|rf@VF4Q^bLu`1 zE1HazXRz5Fj8*lwV#x%!HIFzj7-}mbvihvziNm84fMRJ*mn+ICdK@X3R{x+}0OsE* zAHS^@RQZrNB?}DVR=c&_ljji&P8Pi|=kTaAS*M(BAZ9r9j+&DO3zV5M2i`;A6=~C<4pa(r{6>_&r zx@~?pkb1PF+7(bwGo#$CID~;C;=_DTf9csTc7hdA+{#vY?)vOq0 zfRq8z^sh7Ck=1dEojyy?CdjG!hL3dtMP5|RU2AM zH@RI|^qNE~7BBW`pc+8xey}ZZD7lvkjROT{W~xrQ;5Wx?SFe@%J0iOcy?v?Yje~CMAzK6*gSGicbuCmN!Kux3ltvQdKaKq8P0tn!6{8E&-36gh}g5 zjDP=Y?G#|v3|i@J&06P&oG(w1ukba6b5(2YmoJ@9TbhWW7*5RTO#VJpk2gS9&cbg! zqoU&@x1Q6yy4H22=mj&&(@-S;!Ewp%s9<`%{P`N7heW*re|Pds9X za=8u*tZpyf5WSm}HeQ6+|BcolwM?&izEk~Io`a-emqt2!c;%wJMHJDl=+)&_RJz8> zVo$9pbHL}l07|ZOutSQ8dwN7hbP_##tX=*>xI7}egQOa(QsAfju_dHOo2x!Z#Pxbg}9e4X&yr> z?$*h!z;-Dx?iItXTb0V_z>5tzZ&7!ML|+cD*DSc4a2o36LMV$`R<7;5=y0i~zS{4@@AB&iuid*=+e3ioO(B0y8 zF2y>(OClAWQrP+{Sec-y9jNNb~y9Je0Atz2)ql8Y2NJjvXW(x+ir0D}KPsj=k#*budjEjF~UC8<3` z8~<|o*Sw7gz@yUtkpOX?8#>vmX&eiKShMr&8bYu745Qwjj2HIx zH@Q=gt#EM6-Ld-AOlDaEVQ+YNI>;dIHdVrqaGZ%Q-id~fHJ%5JB>Xel1zEMfy{C(c?Iz=e@m}WnTTd!%*NuJgXBAqh< z5R%?M8`9yclpFe5EnTEpXdTgx$7|SF6R)^#S{gWY+aGbJxJ*qgRvB3gb8rNOH%ocC z=fBRfXxd+Jwd|`}y=X|5Ia~oqTE}+NljfeF&s?&Dg{D_Cmig6StLZsce8<~m+mw=% zR>g#eaTngAJ75v&_3Llb5)K@p68<<2n&7!7?mO4=QVFfYC#6X~cI{T*z_o(BS<4>X zir$$9lTx0p-Rp&7GL41@4t&eS@GHdoJ>6y2Yfc`900n^D?En?XnjF4sN8uFWAVFC7 zE@4eY*MTp@bkTeN!aZW{rii!4)VyzKbPRs0v4}u7EE#+ zx2Bx&D%^D)T0G)c+qPp7X{r3$&YOK0;2e$Cr)1>sCX`NvFp0{uE4wd8!@f$@EMa+4^ zcUk{Ns_8;lKBrIIn&JqltZ|q&SS!E?7Hi0{$bKAN<+PB`P!!LX4n(g{f?<<<7f3;Z z;#DrKw4xhz6P%HNb#W5Md7rEIOi@Fbj@>G|69g}dzC$$zy<~hQYrgWFl)-c7LfO*O z2I}w!|0en;y|u^~^>Na9v+KBN#jwjh#>WcF|G6 zM(>n@wEe>WB`uuUq z<`+G5gkGIGB@PDs41f8;6sj@FFljG02HC?Os%zrJ)xqW9xH%$0Jr(2kpeoB+Xky>`$TZ*3@;Q^+z__7#x z4ibr%@2Y~F4KhE44vp)#TDGYNM%?FkC&^b*1LOd{xZeT6+R9th!SgSpJ>%2Hi&H@* zy7S#DyI1D5(YatiW1Ij8W5E(6ri|(8k$+=jrEdk39rd!Oxn-p1s>2Gbcq=|eH+rVJ?Q09`A1d>RNO-+BzukFy4Ruv!r zfEyKi(PVE>)Bz*82=nr(Lo9!a&avq|?SWMzb{pKlF{XyI^%liVM_SrXb_KVE`X3l7 zG0jV2RidbvwEvDW-qW9-n5Ykkf#vm#zIdeeT@B+3anb8{%3@Y+$4Pb9dbH@nQyU#V zs+cI*n*v9{knkJ!d!}z!+`mUAZ4!#NUKy}IGACy*C1s}D$QM8g+=J;3mL!Vx163l5mt7~jDL4fK@vx_UU^BI9 zi`R~QUiGWEMUf8;VWJU_K46nn{|123RG3oD z+Bem|*8+GDUWF4u6NM}Grjl?@lD?R?Nm&{{RI>S?qJJ4r2wnJ|1`6HIFnSExkE4e@ zF$vs#+zMivTtPGtuC*MGpB<{KaS6(4NSCk z0WyR5MB@kV)Ap^>-Hhm^dCp0NWd?I3j~OIcyPOor2|v;O_#-N{In{9|Z9D@BE3&e( z{$Z-Eo!wpAWh6Q)KRC&oUbDlwrFaWU)_qYx-CP2%pS~xfi@*sNv+Q&a<0t77!$8mm z7VYG&Q5EHaI{X_${Gpkt+p!?7%uI_(m&^$kN2vHN^BtKNnuA`a`n66axS1o(rCmVm z=#^Fs4%nfs7y>B38cEk=a-WE9i#C~>*mpSiP<~s~bx;E*pNmg}_kx-mdt5#D819|c zO+0Kh``~gYeO_gm5uQU76=U-GR!u3FF%zG zNuH$RqeXZ#Y88mFEe+k=QBgYMUaHH@_VC-=Haiu_)k5A`pAHC#nNf!zDyKvKwV)$3 zx!Lfmui1tk@g&d|_hFf(x!lFCl+ENWj7f^>0JI`J*b~4bS3?t%(!#?+XE-EVh-B2hr%TJD4Zb z2(4~9Ywot4T$7l0r`t|t=;VFxut2E`;-r@Geg_w#9N0ln<9h4Fj^~`x}9+5vSypspNlyK+nd(4s9T0Eb&Z@~X>7q_2Q z2@?}D!wA1A`gR00ml69+8%Y7vRt$sKMdkJZ)Pn0Pq&??2$V9khuG#ezAuP+?#v^#K zJ@zAC@ZOVd4Lw{_8i$t^gcEo7Wz#p0#ijzPj@LgToZ?OLD_6M+3EaX!wVoi#t}hL&>Y&WkIw{f;rjPY9GTk@*#IHB}R7t!G zOHOiYt$(_c(>6-kB+Bg5)pUo*($DAFUSFRC#?ihBA*Sq<9=83&CCepI$DCNClv4iK zh^C}!!gZ#^#{5e*J=cE8J&jdM7{K+fpdL^YC-04u(0@AF;>8=YeUzo@8RUCc6AGWq2sO6YI-Zt0N2U5`wNtNg%)#{hilL$42A}Q8G^rRb$3>AB zsdr0ab-*TLSgMQ*jub~NZ&`d*QUrU9aMmlE1I0^g3qZ!u3axzQ1UZR}ulzz?9^X z8Zs?lt?SfIglk*T?#_;dvjao%2{-fHsOu+jNLv9>4%tM>oXH*j{RV5QRxi|@7c^zt zu67`hfn?8)`Lf8LJgr&$o2@$JW7BMZUi;fVmY{^TEAOLJuil~10`htjKI6K?Y17@b z6o5SE-Z!sDU!XQ2IMAsi|9V7)&*Q0BUt{Z4;OQ<_MbTEk!Wkca@FRqT#U97|h*xya zoa*#E>1ua%Jxg=9TFdli-6w)`cZ0Dg>Tk<@X2GlSWF*CV^clPN3>~mOk2<`2NRJ;@ zf@~J@_WbI+x88i|)x19I>Hz16-l^!$Us-wNtq_jpJMXmUyCa0_tJ{AEYDFY<>Xz$% z4=?i$yq=#~yw9O@Z>VAF_jpz;dM5gzNwmL^-4z&)Uf6P+D@rOEZ&(TGHb{p^>XcO4 zmJ{!+v;@vSKyV&`nmmSj$MgF_SoHHL*#}0Nbf5S?hktGAUIkEIuHx`t!}e0eTKQW} zsB(pPNv6QT-j0DwstQ*yax^ndN?ifUW3mY3lkJF|YX!)2u(VmwF#lutz}$VmIE zYQ~6ZCW0uNq$Yl6l}k;uJ+P_F|B*G^Fj?Nb^=7j$*Lv#lskR{ssn9pj1eYW^eY7KD zmD#94PjkLh6*-h}c-VI$WM0o_V)2A&5g zt4}H+lhmd2@wagAz_jz#zif&=pexv}n}TN{g>@5W>hx`yQDsAg>^(!EVYAX7#DUNq z|GBj0ad)Bq(Np#=ZrJ1Q;Jf)goTv6i6sr4fiRd6WI>!q^vw4C|(}}mT{H{O1 z=JQnVNgkd(aY$Wb^xvm09b{DQ)7B2y%M=wxS(W8LseM}xXjYZ-c%Y}dUn5s5nU;6@ zTtN$GYmiSloc6tEp7SV$WFF(fQQVTTVG2ony6Pc})A_;SFm`=}%@r<#|40(qt9zzh znE@F&s+BO>AtA?$d;Tuf+>y;}rz=2_EU`}Nr0xI#Ufa4VFP;1#faNmd-(r_W05Yj= zm>)Qzsz^99J$wsbn5u^I!7a_c4RUf^R@wtW*Z#+@r$5pe<}Z5!M27?sJDgc`3}dZX ze&2CU&uf}UBu^9lN>oYY^-G?}O(d6&yg?J&oi7zT=Cbg442o9kFt1J^5N0U*j`{Sy znTF1_MsSyCTGRCeCD@_-2~aQ~tJ*(&m&g0udk6-ZKiG9$xO>R;$Rws6{wQeCAu;c}55T=GaMcNY;M!P>sY!|io;Q!#^4!G!B z*OEfkr+p1K)v&$pTDfB3CO>_+XO@haM(R!JvQs+IDZ1V4%3s%VdC>j5UY!c-%E;ik zMvs^+<0?cv>_#4I17UUqxZ9Vefs=EMZp6{F<~?xKg1P5l-gX5vEc&jROJ$t1-EoLx zOIAS!) z?r6jHpgUJ!uQVya2u=DOcw0OhQr`^JS2ZQK0LIg?tJ|vwmdei+4ylHz1ARapf#CQ% z@brE??zL%80u)dd?nZ{yL@?tZ>Dnq+V}E@7yIkWjk}hMOK82rX8?RA2Nl~H-C8;tt z*6uf-I8HX^3@a^HvbAb&kSA(yXbj(;v9^u2Wup}vVUf@b?+nUaJNTlgs|j+vN82;1#5zup&_v=JkF z9aNyyF+;;f1b!LWdPN8cfnZs3atk-gW8DCUXd_=WHGnI{z&oV6*7UISNf3_^vO!|p zmS{uh+k252wS&4TwZ7APKELZ7RO>U*>rzRZh5NE6dTf{bxdtv>Ck|(J>7Eeofdf&1 z%b0MyxH{cw?XY72L+Q2BMR@`SIKrq6sH6q z=IJrmouGnPMTj%>mp%P!MVD?Hw!Q-SOgCo3J5o-8ll3+tx&FzKuVPvr>^zamHa2cJ z8S*v6#X$Lj)tO4FHEmF($<~$6MywuQB1+V~J;m~KtB!TaNFyhIZnUQiwzAujt26mMV{DI(TELl%*oru$ls(a5+TE=% zk89z}E0rtcyHK4w7BV+_ynr_N*-oXchF}$Nr0;#g62+r6Sjp08w$DG#9u20X=pD=H z^*z}S^C%}E@TG=P8+EMf#Cp&Ry{Nu|&zKCs8B2Eth8K5A16GW^<~H^%PQLMA z@yKyQldMVhQLGo7(AC<)+cl$dx+!#G9vkrR7j{phv{d?9pfqShCab)4dlQwc;VS66iYo8NqTwj0mP!YTT z#!TL(^7Q^r&4f;t8T!hZd9HwS5iPm80Z(wx^Ep3fo890zIBSJNNJ z%Fo4Z+VvzY_z0>YQ?lFo&c!Rz`Y?h^n~I!GIj(TVdp(Vdk3(DXE;Ffew)g>`o(QdM zwh-at_tN$Oo()3(m~KEOq_e~Dr$)V3bsq|5>&WVREi3g2bco?jRgO@HzgnBt{K7IC z?wB(1nL41UX&d6p`y(S|b<9`1KUR6ZrW8sH7}pY$@ViOh;V^4ts*|FWe#D#6!ZqMpyhtXrh9&e z%3dWV?tgcf8>xQr;N!7w>}jSvid(*{@GMRtYx)1?qcHBe454F_g%4wBLF@tsY_i`t zt3}4hZ`HjrXAueaRPaj5IZ+@U9^yz8`T6R5Yb>L8O}%x-_-Uuv9(?zjCt29>(Wr#G zVVt;9cV&DA7Q(Op!qMcc0S&A&BjW8!W|L7Ce_;QKzME}ANH|G?R7OA*%C`yKj;m)pGXQuGC zD80D&mc#k+5nqj~siAQEr~4PfY*_;-#({fs!c+lm0WRHzYz>VFTAq3349U^xM7PiM zVn@ePjWM6~>m1yfQ>KpDd~Mjqo~VeZ`7d6a3s${XB^YOmiTwh%wk4*{RMdyQ>0)dj6 zvz2|K&l8oGPqKHxDp)|F&u>$BLPAyjoB5dqql5?^lIv}>!E#GoJO5tIW zN|*a`t#-fEbwI}8L>N+451LZA0aw|IfI?ydjDx-zpu8+oCYE)LehO=PH7%WK{L0vz=y!*&W*gw zs<=Cd{1r3i+Nl~PmhJ1g;EwOc@Oj?zqVYn93;u0{#Q+{1Pg~v#ovL?V+{RVPEPXEd z|qsKMug zQWrx!x0m9}Cx&jBOJ3>(4Ch~#ng)!ny+*nPe6M)^vQkhQyTf%{U2$ANMUL->$>nt<7@!!5oU5RAwD;{BW<;=&bd@0m+7 zg~w|Nkrlgz9T^>JA|>S0Ud`9^P>5)dU$jWp#pR9lRv2SxvmV>$c%_$yfqgF1H~r;& z#n|$V;2Z6K6N&$m6;%^uy|kw`vs3QGy}yZXjaptCL|G#8m*2rocgo^s@+da;NJDDL;@VHd%H@WsgvOG4nKV)!^xj;TVNMDp;vSfduSLzGl! z`E1sh;$yTBDYpGsv_Tz1ztgE44g38*{hyp<%1lv-#$A zma2*y>-4th7Bk*kPT{1RbK3tjQ-8i&rAQUE50h+wJs0&nTto2&;;%^G3hsYEStvV( z-VW}07jb+YtNDb038uYwZ#?{#LeixhiB(m zu4{#5)oo>mN#YK&NcbNiQ}W5+5g&-keYIbS?{gRtP2$B$K;PvLdsqR6)mM-BsF5LZ zAHsPzxajW3Q6p0^Pr~A&>Kr3%GaVDEjOasR{&FNbUDd6#v{H%? zs?pEAbTo8v1uMRJ6F2P011;;zqrmed>94u+XsR*ypm*P9edTvqhP%PLbFt%JfET-9 z;60}cc=C?xvj`bRA?$vNU*;X5#ec1%gDLN(ihyFG&Ns+)8(eCSH|J!_>Q~;YG0*7G zouVSkTkg}mxmZ}l?WfAm+@SXKu*Mo8jn&8>i270uVEJEMmIbwY5ASoWwu(xfUYu%|(=Fy=l^Tk@y`V{x&WHt3<$4 z*~jn)k9!Ss=v87};=1@NNPsAAVqU)_5b)Xs_mZ_%K|}w;o%ow!3x4&Wg6*Z$#fq!Q zc2vUZFz%jn^mB%&tHF$}M;TDx%M^Go(WC({y8hhfTz9?X`T~Z(I|X<(Ec$K^2-IGS zA@AkD^dVwHd)9C4_U+p<_zTXPu>5@S(}iZ2-x#O{d*5%9nZIk9@;XwEiDRM$!L=jKxt4t2zYa8zpdD?f_qZiL|R||XSfy0(W z`zGPu>-UX>wgD6Ef7_wuAnl_@*jK+Le3!Asa4mSk`rsed*|tV{(=glin8o?<&$aL` z@1&=(5gh8EOOoWPuw7;#UUW`R$bU^vL&ccNb>TQ^SSahZ?$uLs)YiIt9S&?Cn!1|x z@A>7xzB48Wa-Z7rqocmGPI2gD>R3(RW9^S;8TcMOq)lgEIdnVl*&-$Kcu!rPmeaB6 zs3pA>mMK}cSYq$xNHa6@09F@A@Pfcl9s`_U4D`Vqn_AeG^1vWhVv~WN9QXfR3-;Vw zblClPF9rVE%oJo`X2qc1&>RGQ{`KX{E~c30>_BtbaQAhceGGx2+i$A2HF7W+Mw&2) zT6ChtP#-KA2vnab9mNXev)2R1kJZ{h5R7WV{QOU!*1H>2$o(|Pn8~hL?Bf&P&m*;-{?=eO*o(IVz7KPl zZfY3vrYk49hW)6h_)P~E7xP%TucSW_J(MBZ7)L_yzd-+K(!0jk>%6wgl7;Rc_C@** z;cpqnFt{Ht-Cy}^!8#N=>B+1oTjQ>y*emPnkj-0|2{KoxKKZPu1pAA7t#rQomH8{n zsy&bntdpr!5=rOiD_vdvm2%-(PNiht1Y)`wMF7AS@b8B5&b@ybX)%46G*Sw(vY9Q2 z8^Tj#uU;Q??`cVYHl0K>MzJ$~Sf*mQ9KW(o(Hjyg$w4Dzmw02R7G$tGhuR;&kgJn$ zhZqC&5NUHX!Q72dp&5OJCcsPgN2<#{DfAMmjR zD6?ADxNF%rIgE92yv4yBD?UgEIeYI~6L-zguu??hb}xv|Wl%j_DR$Rzu9Bv##URsb z?Y8&Yc}ne(jNuK?_uaGuXLpJ!KjTp@z4x2=8jYWTxk>>l;6Sb*7w!ucIfqjT(9QGj z{cy*#;yW9Y+78V}Mw2paa!mDzZ$}1Xgb#S)JmF{sIa*PJrKKegP3|7Df4I(OuNhUs zs}H-YBPt2kUC0U?FhL_D#V`(M$v@X6Kl|q|Us#%b+&;B)t;dr|XA2Pw8rQ$27PQsL zQ)0eO`c$aJ*5B9X%#7dJvnRe$6ish3O8AJCb@!b~F5wtZa(S%#Y*_4Wd_M~XDU7q##WYTRE?5dYoM8P`u zO|XM?{MQ>C{z>BhZ&Ciy5j^}yY!r^~Na%hig@$4y$y@kzVkdeXneQgnz2GXZIbKA) zzfsnr7>Nj4QUnR$vi(CkZ(#2w(L ze{JbM-58?=dN`H_jwk$I8p2owDCdVTC+xq{G5>ZgSdSU#q27^}3)4USALCz+Gcb%V z9spDRjwa^6KJZUB)Iiu>as>6TV5@B1_D+<9J= z2f?;H(wD^(jwEd1w=jLxvh%uHB@|?a@euqsK^?yy!M_G|W*R{>qLt?@uH$%&jL$EZ zHGq~^a!OxDj(cWq|M6(SC(PIUYUFUiA*z4t`(J$wb|C;J@vF(Bf6u4>l`+X?y?L0m z1@!gF{~~&;_kh6@X;+B(XLz``%zppd)eCZ6AJhs33|z zQ#B$=BNL9FFE1}8bd*kbMVXjn^uTt!%GAOo^KAXoOhM8g2uRT{J_g>ui2L>!+cxUf zLF}2fxn#*#>r&Di)#OFffcGDxSHOcgA(HK0y!Iwv1u-SBQRO7r1J>= z<6r+m2^IbS{HKE>C{mx~(DF9)ef&e1c z9b?IO2aQdNwnh66-rvoN7c?foEKLNNAeoN&GI-^A-Qs!cy@@N>9)AakGMrx|Ho z_Fw4}DWkD8-1&4*>YsBpq57N;m#(Z7w;vEiCV@_*j(<+ZzvWY3T6)pwZpVa6w8^=C zW7oXyl@Ux?p?;~t|iEj zpf)bhyYW`S&eozpl4l`PfXH>Np@uY>_w@mz0KD|Y{>u_%tVn2!>+tQ9Dwmwt$%0sy z8eGH*|Ikj+5-9LQWsK~8qLaTW0~fVwaO%wxd)@TQ;L*Q`v6?%kKLi|ih1}(C zm3)@-8@ehHEIR=a2!xMpexDkNmM?F<2 zT#t#)dS;yI4~l1NdFUSAMvDy;(!z$`g4%`kIl^E-Cxtlkh%Z_kqhg9XJ^k-;(usI9kAIXxgg3C(Dl& zG?~{|afM7!;%iV_S~ho0R$4M{kdk{RZ34#r7zpXAlYc^nK4s_oz)X6V@%jV1j?ArK z7rf3jv=|pIsp7whc|dxQU>>^4BpUP)98XVwRV(zMyAKj;%$V}k`}5`UVyuORWelQs zAVqsVi&Hzpx1tF>_F<-gijS{ZxJ$Fe??kQv2^^o!g#INDP0&5kQtDnq2xRGu%5jVs z=2KS;W81wA2JO)?%U-xUokWcdZP}{x-Wb>j~2>^zWnND)G z8bN2;VsPy;f?x@rHatvUz0|{h8P8zKd#NIxwX+w$b^4%ui8iiUTX>imZpT3$DoX>3 zgew{7uY5$?lL@AxRnm_BKLbzSzdfJ(?Ql3IrAlZMO*x?AuV_sJgO@8Qpj;Ak%3h? z88{A1k&aHpr1PWf>JlL|1vmhw4@n;I8Hr>$Pvi}0NnNrXhh5nwfcBJ%6e8B1X_K4#qvGY$hi1E?yZ@T`IXIVMSSeK8@ z3uk?%)kRmlo8CF}xvO4hIiE!4)ZG5?!R7boG6z(2Z2o|}@h@l9by$qa6#D;T?>nQK zY?rm^pa`N;1Vm7zSCL)>1W`bWfOJCdC3K`W0R`zLQUwI*1VV>EPUD1*$JdQKp!6CwfaM{Ism@Jyo4<1YWhi}DrHrX zv48pi3+#{GGpGoPHL~B7q`1P%M0(anwNjt@f2Cgz%us#&qsrHdRcNs~meDF|(PJL}@$nH9GHWIR}qk83G#%)Bt&Cw4({% zW5qJ?0P`mHFt`fq{^yMynr97p@Tgd@-a0ZN?+^MtI4(%2^)r7imS_>MZ_sVKFjhqx zzaV>=aMH8C=*S?O9iW6z_c73`mNY`1ybYK1pRg?t4mfK+&UuZeUuVN^yQ-J72lrZR zu)X$7!n4v$lYrZ(LDt8X7>EA338FI>!i{Q-Hpp}AQy4CkO&BdpWTH)-G8?%)X{O28 z-qq1SP9DKHskv_56X`zjnn{e~HOBor1t`XtL@Qq=AfV-=_l4X9lk{0Oex84mC!|i5 z*?A{lrx$v>i+G7AT+`*!b)Okz=qKB*S;HZ!WH4O^M0xTV)EXYZy$TJCTl^Y)9VZ4Q zs22^Iga^u|#qtXZl%uGhXF7xk)*L4v>7>sZm6wTQL$%_ere|j*PqzrIZ_{J_{N&ru z4N3$D?_|!h*pmeQW|~;lA|c>?dM@L=bY27z@@=w_Xu7cq13`s7(2?IA?h_44$tdG=%emuqK=v6({T+dmena#`{dJICw(Jof%aLp?4 z*Dq%sE`A>&cV@t8+#Gu*Y`xTbD9z)k7?|~$lQg9~u>}FIcT7K+3#$vH^cD9>j0tj{ zw;bx3>9B>!OWd6~o-8PDlbML&$uo|vU0TbVyS_q$=lhZg8=SU`VMmcjZC5}=mhn@C z{llS{PJ{AVjQ{EHS?z?q%`xQ`%!xtj*#hP9PFIoZuMVB=J8DlCh6U}XM^zO0E!Wfp zWD6d$H{7`Q!#E)!VX_wWp_aI>Oy3UE^{HzZFUGo&H_3i`J|6;q`in-&JSU;J;CvP@K=ED$SApXFz+q;| z-%}_~6%&&!&*1_Cd++_W&x`X<$ddvMA8R-rO`M`JN1u)GF+I%0id~*6`Ic2g=uPV< zmW#$&36vY`&ZFB%h{=?XSJt_2ZF1EGzt&*mrln*1N09l=RW9E!6)|=AE zc@cciAEOEJ_if2=IeRJ~gQ_FPcbRVcmDl%q;x)xuV({LK_y~FSef~ z-CMt7+<4X&!Kk|~PAB5TeTdi%M1upR*}*Lb;S%F{islXbDd~C2pg|{zBai-{%u8i; z$mD_4bj!I`j4Q@?kc_-zhHHm!8R^PfABlDAZwMIk_2-Q#?~y-H55{Wx_4Yh=0<)J) zsuwqZl`}THe?Y-Yh20&|O-up2d85hqPXW|};QoGYY)ieejjvj{1#0A6(F66K%=doF zE_rRX>6jtLG)D;gd64zM5t*16TBqAuwN~${)7z=gOhK9nWNDN9bP$-^PjA@hJ z^X%^g%Y(+;42O1g@Fhzt%bsPGbnJdl?Q{;SX@M*DkGpXO{Tq{l(8e|=Dn08@FZ(*9 zX-`Dr*o0RckI+-r3yVK5BsuCX7N}`!FIp1Pn|RC^ejPt71M8-8`uloBx!1osUEwq2 zjHVOf*2I4Btvfs5?vR>ZDkh9KvK==6S)%2F>d>Ub6Knj?!y3qf@y3~;%Nvq)eQfc7 zr=$EINlma66Gf3}3lmj!|GQq;1;pYZojp7kHp!+Q=a2hu(YAL#fW5OnRuSQf}^Kl5L5rGIwX|9NXO zC9EhO?|ZsoPdWSVxSxOg2bNzU{%EiK~}lYLr2x3}DN0h2heC5C1QENv`T?&t+ur;mP{oX$*aG@&}FH1S4iB&I^rtoF3LNr}Uk?6c} z>Zh0QaPWk%42(`Fl3SGuTNt60UAhB)#+8p{isG=Q@mG}VbV;mk9)e0PtI+=G#{c!U zfBniXCj4ZUT|A4s_CF#9Zxdh%rEA?i|K6%(-e8&W|Go8pN$P(E@?WyJ`t|=T zkTtDm41emLx*UPPlPbIJRM#{tv|%@wulB6owZjA+$CKb*+Qbf+s97(A6AOlgGI|Tf zVcJTjB%@5129hljrTO>mR8F~Uomk%F*JM|K>1$|$`JTU29D1a~%X^zSNJa?jDc>xJ zL}<$41WR$MBaiA{sD*#ORrvLii&vSwtNQOC|4$1Hj=bc+pWA6r|5JeU-@@{k78d@; z_M(4P|6k1bACD+}c?rsStd{?@z4_mosJaT4miyn={%5WHUupjtDE$AVB*EfTdUyYw zYO_{2?^~s|IEQkp^d*d;sw|BZY8Rb`yrexPivSFYK~V6p=u^uiNU+ z@^}WT%lOrx&LOkQ!~L~2wUC!z7Da85GI`HKMOSAY zs!j_IhjL%y3>mdK(Y@P8K&#Wl9>p#x|_yY;XH0BBjQA<@E zB^GN?4+x8I1cErFUKISTME$xP$`d`+;JwM_?-tW&RL8TKQUu5$y7*{f1$xM}R z<=B^RtofLTyRne(=EmpQh<|o@i{i@Gp;fmf)SE<$lM~}dTzoHv1W5wu(A?0DDVcHV zk$DSyKsm-CabGOm63{7d@#$q~r0HIRmjuiZfCb3lH5Pt*%V58AtD9IAJl@p?{%vegNsR1rg%2)Q z_@KGK@YbJ5pZt)2X<{GUAK_-HXZO|X$z+ZB6d(9`KZ??%E~1&JToiRT`+|_n2Ed$p z2uIf^Z7x^le@OI5ceNaaS1oOP=v$hPm$I-qq)v7<(z+@RRx*i?q2zZ`Z7D^p=U3M+ zo2AoWp1BN$!R9`(di?xBitSa66n}MWx!P8n?%^IBTXR;_hi%yei08^w-A;YhaMkI+ zsbgYw!iVmK)>-N;+nsNIjf$o)nVr49Pye}PI@bQoOZKP4CRvgOWmm|8?Ab2bU|Ljh z#P-_#)ZJRY3cuV8_<{}X476$N`_)ooIRb1xA@eHoQh)y0dofWHXqU}$-fZ5l>ev6Y z$*Z$PZ2tv>*z>6S&`jU<^qoECaEq9Rn!27I=GhH5cK;&!ThgQp8bcUho8~-~#@(>g zu_~ZmaIgO0rhmSF6{T|L<@2-}TRY3b(7*QZ^dPFCiHzUp_s7iAc*a$i2}QSrY#6!h z2&%Ml)PIjJ2*uiSSDmLwJLrM;o28_T#chd$znruyCobmJ)24iUgMC?(Skls|M%Ap; z^MrmRzcC8^;`$GqW~9GJ_2B0)URgg1f%B~o0Pd&}oHcQ3-KS(h)XL3o z>DZL(@_I6#01{*e8vFA7RpUH+UP&$Wpf7d)XakR>#T9da$ASBIsa%2)I4GLV`B-kY zuRjbWC*sm<0QHm*aTownk>)Z0)seVgN(|O+ zjZ_Ik!-@zYl5O_lTzceiEdI1+Q6A~NAgi~XHxiII*U^bS9k(Vsaq6(QLLYcD5SP{^ z(v+QT1#U)tK8Kv2tXYogz|4bLJ&*Tw4J(GmDA}q6Qo5rxFSx3Nd69{rms6L3`3h?b zOka^sywrbubufbF?IAqh74;<1Qn^+&sVqteFcs^CFY43a)1))vor!DcBMqi9fwT0H zh*MAgCh%mOvVY8##M2A18-6IRksjyhuqJ+AC zW~s>6eJ1^J$_c@ER`1?)v=lkwQ@cdf>-%Kaa@md{I*bteMum@p)j-jpra(V=tWnnj zUFlk@^?EKaU%5899bk1Pqb-aR#I^R2q{-M-IS>1v;jKEMR(+x_gCk2^;%d~RvnHU~R(%#P=kpTA1Idg4P5AXigl96AU3>3Qk`&jSQU>9`st0 zqR)Rfk_Pk2OdrDVygh%dKpgsf8gdUS(~wIQhL^q+2XBe80-W{N&%a4aMn2dq6JId7 z$=Y7Txy|kEp$j6~u*npKkgp}!R0TSJo@n}say=oePjCH27>2m(*?=fB1u<2B^D~Glo!ebZx7<~x*rGgNxC-}$p#IRO{6RV;g6LKTscyt>*eSduPJX0 zhdC054Jh&?@zL-n$SCU6Vk_b12_nUbkI44v2#h`rw=N1D85kj-!hy5r^JkrY6Z2cD z9YYHVcKmM5S$kt4>Ac!8efD5pm?J7yC;ltR@M`e^$=K5Z0jGf*-e#DBl*~(IlQ9t? z3;};`O3ifm+xK2ZqUz~{$a7D{pWV1VIWFH8PBsI|&aXS6e6Bxdz&A9jdopQV6zb9R z*a5Z4A=q?`>UFzj_#r?62t&vp#1bEu-3i$^^P9G#eG;}xltBZ-BEd642mN4^MI2Hw zS$1@8?5qj3!%NvTEd zx3`0iuyv7C9NVkIJD6@Wt@yGwMlb0N#P) z;OUSD`40~;@75S;Q0@J8X`=~In_0rH{J|fqL|+@sui^;`cGzXDEl`2{Z>|9gf@aT5 zdcmlr)yFVOqmn?m38yumI{S@UFrsk3>Dr`A6V*Dys5#Pwt92Yo-<-n(8W%b`U2X2X zs5nC+%=ywcJXcG@nWF1<8(g9+rW2yI`LXrgOzIEtqK2<8jif%0-jdZFDZCTuLY)Y$ zUxbCvYfW;zn@_LfgK7r#ZOvx#XveU6?5`VstYa(f-vUwg_I^w#Y7>A&Vpk_gb8cl% zOu%O|ofky2`5OuE8uHdVUCHI^o6T0=It?0{`m^E zbhMY-^uce;HnjGI*AgnD`mp)Q3vFP(=hRUc_Jhu-gUR&5(94T{;O5@zkT{E91vX;` zjYkXGA;zY*XUQQ8EZ9Zld(CE5F4 zHAc*`R#@4mNs4aRo?~d-Vayr*tQDuTCfTt~*LOn$6x4OwW)y%oI zl+!7+U=r&b`VwDA>oHwC_1(HHHg(wm(aW~|?IYebY(#w#iKgjbL|A_lSg&WOGOGPu z9Hmyv*KOW)riXV4BDO|i0a-s98QhM`1i=n{jGSHIK;M?`32}z+is#jSs-N4qs`BYIwiGKx&rlg_kmWiuoPJwdHH@`hK*v&y0V> z=&bB#EoD@M2)3>Fbt-`SMo96-Y#K}@OaCZrMrd0LtQ2r{x=NBFmQJuVd-yLADYo-i zle9krhC~pPBPPe1DrA{K5_4{oWz#deO>TSs}uqBtP4R5SOVSg;N*iGqW5P+G2W|0$BG4Xcc*Lg ziaa29usArG9HhciVUgKMucAx28=`9=aeg~QCs2nXB1H0luGAoA+XF_h)uE=t4cp*~ z@2YLiL@NK5JVS7aQ1xCvU!82vuO7Y1#u^B$)<*H93oh2slU_Vg!kZ4A+FgniX%ctd1lw44lpCEjSlhfXU~49%T6mz< z{d}rGZzcN?;4e@90J|?>gyVAe61lPK{hxmY>)x)QZ@4%&Dfb$BGeL@AKJ`=^hbgY} z?+KT|Hk|G?!P|XgwkF6&3JJ!R)}ERFg`*0L2SMJCs28)eN??=8uJEulfU(;;C$VG~oQ-kdAhp-(?xLmFt1Nc-ns&58pPen$(@NT85!)NE*S#MTkZ{S{bn+uzFIw6LM#oXz@G3YXGKAZ zxA3(*A-^cSS$V9f_FJ?e+*HH`u{CD}hMRtXx)-dz$ECS9rb*vAZ=p9$?d@{*>{#{% z{00!DRjC!R|X-Wz5HObf>(F42=(y7=Tc^ztUHJ0f?Id~z9x=JVXDL}{nR zkRErBOkINeC*n!DVWjpabdip-3@3Ia@oTf$Rn`3yjXt&!1k{($lf;`X!^oyM0P^*q zN4H+_P}ptPwdQpheuRV=sd0eif@tcX`|9SfuXX3L-qb@-^V%1&$8bQZzGoc3*oZf9 zG)SG+3AQ}KfIO5=*XSr|8|H>5@EU_p-;mpn!LhwzmoO^hj&(ZnhrihW#MH;kCL+4V zXYKU*wUf``xL&aFCR_Wiv(q}>R77F(YH9NlNv1XvEL%N`?zH6NF@XNS=xN^q)tnRd zoIJ$d*6v!dSAW1oHrZ)nfF2tg!>A&!I3&U_n}NKs+K%ZEwp(?74BnI0Gu&`5=gu}7 z^e)r^=u;3z8Z-od|j!Y>HmKqUjJicQ z7`>oy#+|t7>F)gjPp42>9!Tdwra} zR0CQ9>H@0X&ayOhbM?3HcjOYe9#DE3hU7kT46HhFamC19OGsiz?|FF(n3@JV^o>Bi zZ?;VPu$RCR!21LGModE&acttGQ5jXh5OSxCGA>nHE9gD=F4<5&YpY+sd5E9zoPowWL?*v0 z0X3TVq%k(B>n0R%VE^;Qfz!CreTg}+Yrxh*ev2n{#h$Q6Fi+0eIDX&$_hC^RH%L8; z=uN~AbW|XCFJ|hntEM5{a`|g=!2L^t2$vxD8o@1LeY%)G(I!ad28kc$3mH#I6w+@$ zYyD6-wBXFKuPGJ9IN=L(QQTKH80#=^b5z?1phjlbv{Q&HC|&TDg{ zK=n38ebq*8Iu_kL7wHe+o|N4jZQ{MZLg7tpOddxXnX zACl#*id8~E_xhvP0?mwOhB8%x)Ggmf#wR%NG1Y-kZiG5JSgi;9ukT6~)x3#ye7ZN6 z4rBFR1P`q&^*v(no_oX)mi;_5S0F@Ew)vaA4UENu(8uGvc;rUKWp-srb>uT94;oeB znv1w85*bu>8r-REkSCb9rG4>ewmgfICoyg6TPAj}b3`@##r>z-;;V7P8UnSJJ?VWd z2}9ay@nmp;{A_;X`;!*}v{M1bd@yOF;ACxVxJF4CeU}Oln9<$@4lKVe9V z2W>em$rJQfrlW)9fF_eE{;417FaP--7uO#6SzKfQ_H9ino>6cOPG-$>?bi^ z6w3=wPm2DZY7{Vsiz2x@k36b`w^4gIgyZ$tF+3kP-)yypPeW#8VfT63)u?-)n7;{rHDZgW zdOwb7-`#a`?rrsnkrhmpG>l7_(3xwRt%cG}z6tl0=!ik6FFw{f9HpY{egxbqoWU_)gjf30RxgGK9ppV>%yD86fUoEXkm6h)m zsFUM^>(>}9^=e)7EM&#FD?%)M%+}Je2l*gs*gY3`>C{YU@a(H)5jO*o-Q#IbN%W}A zXM89q&mr84la!Tev1IwRE zOpLw)O2e?>bg&Ix)yRtN83}#kEM5$$Jo1pckKX-W#g;*;1cU{ z=ev5tG?%g-=yZRK-dzGpKjkGAE_3!)-EjQ`t^G|(lMUUn0}%KDrw(29S3*6oNZF^9 zXkZO}bZ2}Z`GVdrC1dKCqU0w7*9pdUKvz3tJ4v@ob@fVMHV7Ct{|isnMx2%>f3ZEuD0_;C65v2sb>|qd!et8;|#I6q(oqC}$+w zOXrZ2$OiP~^`WsxRK{H5<$K-=-Y1mrLgHv5Wdf31U3$kuiMW)1G+1H}GPs?ZPM2bQ zc+ps^E-o0I*hgN(9I>~jAYS>TLjAXfzU;{pT?ryXKa-F>I|jDh;j{W>h6FNkoZjMr zuwhM9#bU}&KpUzK!SWqY$lRvD5PSvDlgX{UrP+^-m|t!C4@8Nljlows7*1Q3_%Mgy zDSGOj=tRa8x#=4}$u)Nb^1K3jw1vhyU+ zq2A-FAABB8gpMXX@>Ku!g_M~G~yynpyx$Y;}Tw**Cx?8d9K6_DZ$TfXn0MeFD z3q*+dP&J)!7cBjp7^7`CN&jnoOrm#q zA00pLpN}B5t#zbttY5&`S0L-kgxA3w>`Ew&NQdEH=OcBLy?|-O?BHmeY~{GFWP6F~ znUfC>Klpfgwh_hO*s!K*@6X1Bt%hN%KR>k;+q}aE%}Nh3EjRj`#s=&4&p-z_uZrt( zN%O4Wl&??x)mEfjqSs|{(D)xa?o8a>iu^hE1($8ox~XPU6e9&p?! z#zFd(kDvn|eXVtG^!ZR+KlC291)X>_S~@>n`Zk4+g0x3w}U~`<+8AmWvANGzc||0*eW0j!U(xP zco>rQcR@%Mqq@gk2(21ajT5ft(3lg>sh43hRiFd_*zRc)KR?W8Sku$oNMD87$&Tk@ zuK<`q&t^YIbU!sp?}6+aEPbWK2 zzP+x#%^%zyO|MJYR@Q!M%$SLko3hg3$<=kbQmBDALM^{q4gS;8*1nUD6zB1Hja>I^s z4sGn{VyvB17X_>Hp0PzeHE++c9v8IzaGc2e#mj zN!`%IUsd5XcO_2lPnP#0y!o4M48#s?<7G5ol~+6`RSn`L$)33hr2HNdP`L<`thh)< zk3w(G{0jfDzmz?G&eEWo@Jh`%Z-jEOyUu>4`!Pd!FQRI8yRXi*IZJi(w7|F=b&nz= zu4K0OaB$>}1IlM6GiABb9OYU)LGzcx*Sd80OTU=@boiAI;;N+h>ZwX-*0Tn@4g(QV zdQHc6hh`owyQ6FNC9FvEN_ydyMt97)o^&iy>0onotsCc>E69`l?r45d-)w`WKdlFb zqQPZP5IeUweRL!qe{*~g7vIA@NGRQPv9c0o*O(mu)@Yh)k{y{IhPZ5tmOCA-Se!bJ zIhnw{36wWu&P6viN50}2r zD^#mg-N4I7gvdA{J^AY2?OQ-%asl@uy=Kb{wP|d)=O!V$!dl}5M=Mon^0ZgWT$9!IT8M!P4ziZfX`35fi6{Gp z3)bU=>0Pc=5Cv7?%K3UdM57hxn#o0)V^jjyi(%gSr0;i zH1!zk>15o49RYXUcG|*jN;fvGQ9%P)-2i|Y^iO&s-Y6EOCyn_YBV^{P2!?;A)Kt~Q!oSwBZ z-?CozPH1%Vn(9_lcHj)%x)6!CEy5IhZ(uCX34f}dN7>DLmQ-!sJ?@6>PfhXs>XfRc zXlLsxDQUGe8`>TH@DAC!Bk=Qm6rk>wc3`0@!g^(nv5p3%ZUPrOMtUiL)&GOWWK~C_ zENyKkvE9^vz)+20Ym*Sl3ET+(hK$y}2rqM*l){ce9`juFY@ zk(Ed>PZ4jSXbyR)2A^?uoSuW>t z*CZ#{FF3;gx_ET+C|ux#Yn<9D%KpwsZ-^T?+V{orQ(#&W^>C4+|ew)A@IGIC|DpM@g1lKT;4p(akkI`0aXCqjHh-Z^Na`63d+-jwB>k_$5_GPIP`!+yfHc(arMEvsmB@MnSoC#t-$KSrGxrtsOC(D% zo#J7#wc(v+p}6nHwiL*Jh=)Q#bV|5>Zb?!UgQ&0&lT8_nt@5$3&+^N=P7825)6Vk6rE|tb9N=k1EwZ{If<~6ci-v45Z^i1m;#~uVzIYTT zF`yl%9lYl!VhyL+B8+$^UU>s*Y3y(g9%f6nY=r^3aCGV!=iH?V`v4mmrMof}LxUpj zPClYyPk(CsM^6}|$}duu}NzK8D-s)~q;F$B@( zbZrg-EHEy#KR0Mf2;w0GwuiyOYw0YpS@rd6DBV<)>_$8CL)GerF zXl=p*aIPIWwuET>jX?V5IxHz*F;3@q^6$*pJ_`wKo9fTci;Xi(p7*g^eZhrTy`W|w zpN}PFrUsnJk>{14V9ME?cLr6Uw-Pay2XY0Jmhr^i~!$sK-NhT-F%uLL@ zCJNX?B6Zo#xXiL2Mv^GK6d5)eMl_#hUZi?40vW-cdM2h9I8Z#42&BzF1y5<9A zvxMCnb$h$iy!`cOD8EY-CgJJ0OR6-%YW!rZ8)u^@nMbD3_YIowYCMAs1+EVT3Ep?P z+f3NuIe)*pBMBm@LfCNFXK(ot;oc3}CrnQzWTDD+ukh+$_i!~$gbbIzwmtSgbM0H= zIF4si{mL_02vaHS9SaIM*(FLi+*D369nn4>d0gnQBIC66XtK)fSG{G~yr-xqhDiQo z*GEWS!C1@z>PETa%}^0?CWnU|wrom5*-ga;*D3;j0v>>Cj-JuKKSbxLe zdcd)>{M@93S7h#c^@T>BW%65FO&-8)by16L)uhr6bx*_~bdt+pFa+Lhk zFxkL-LjPx7iL$@>9`9hnR=)eMh4Z@IrB!eIP{_Urim*4xx z#U_n=qb-Fu<%JdB)FR`eM`$x9pD+lQc4tIdbGZz}BXOv>FF=@=;d4v3#ftbgH$!j0 zjo&_9PAC?ij>p;S*mW2JwGm}Cn+eZ2cw{qJMlL33?n^cu+>f)3GIgg=zn^bJ*s^QJ zO5dBTI?T~{fk%4Cy^0@j5d#g#sDUH>f)eA2h+cF{QXPpbopC5{PF*0 zt}%otII?5cbY@J1#P@evPVvMq?em6jy6Yuede}Ho&^F)DP5XJnIn)l_UyvAotKqDd zJE}rDqBngf=n1(@T8tq4BX(^1;1>PeTGtfWO$XmdhI0YSntMTv`X8wbtoRTQD@t4< z=AUyuTB>yYOXuTBC;9OCy;~31K^xkj)(-ejqLAXLk&|O3eg+4 zn6VR^v2#6GfgFhvaZkr1M!4flYk?Ag+3S$&{=#=-9*zyHbp;?D8uENc23ZSOOFy%H z86S-6NvBrooA+283xqvEy&)L|Cs^QDej;3EbRYYs{jvGk>L!t`|1IhX;N(L+m$~)FO`oH)_AfDH3bt~f>G=PM&u>iZ38c5;HEbk57 z)4`yFFXU3?I_23kJ+Lbq2$p%Cxv>S($U5RrHGA00V&m<#UR&temXDFIndU6Nvw#EQFhS&91|a50XX`=dN{yGfuXq?l*YSRN5*1P?6i>_N zL7IfFiJ`y3{a|N;-?B{iXgd?J)1m&jWT+pi*SO>Md}fPqn625WDCzuEBJyNb#HTd0 zivQ7M&0#ilf0ui8xH5RgJ3vUU(J7)I&`4|E5D@~LeIl@}06h1wRRGR{7(!b}S{%#J zgiAT3#d@Q}a@4(>{={WYIy6N2Wl-S9Nn_Ccm4RgTWREF=|HKRVV9}nk$YaE*P(dOa zZdis*NW%3~C)7x0Pw7}sf@YsFf?xJTO`4s{T@UA-RHJ zVmE_iA93i&Je;S!9Cyw{5Uh0Er-WJewuEDh(ylk)%CYTLyqev-#zgfZ&Tjky!m!?D zaL+;w+<>2FhDjJ+O(M12ZP`FioqdA@Y>}_R9IC7N0vn;yD0&R z$EQy^NC(~2?Cq_PKPvlQt$E^0qxjT>`&HyT<3x5j2a`OM(D~k(osO$yTDqLQ0T*bE}9fj zc`?t2Ydh$r-}wS|gFeRo0DCA5qoF5UnzlX}Kd}=8l`VyAbId@o*hLYstPBgStg3pNtl~RnY9`((VYFd2r5h8DO4S?&WUoo95a{G5=1|M&O1z7vWjNIcr$&s_~WDG6mZZ*f3EN~VfznL2}RlMfnsv14d zOH+4+ZOJm35F~$*+RX88lQ~<9VF<_iIix=dQK(EL3Y<_CUh4bhO}4Gv9wg9#{vgQ1 z$R;AB8b{Z$uvpj7BM@->pp9d6O*$}}Dv0lXCoTymn%jhxZ!l2F)c76Ez+dPYFH@p1 zz>R?7eX2lPyhj;4=uI@G150$T_&9ejBAs~Ef2QPtw{l{ z2Km$&`|Xn=RV&}D8z#CGG>m*@QEzru7RdjKY2V@*nlVYzfBh53u&^eR^6*EY-7OA) zXlyA03NNVWV&W(I;3zn`XY@5_m|cYbhD`VF)MwI1ItLD}wpEbfTy2-s9Az?-)=7HN z10sJTTCl>|K2b(ITRQn#ZK=Vta^P!#5@kON@z|CpIEU17PQIUYB-^``qs2dJ_k3si zcP8oU4)jVKQKKYw;wkox+Lf|3;zcJoLJy*X*0vn%(MhwTJ4~th_HOGWC#Eca=621g24yMRKY*UMU&gT0WEjF?o0nMzF zd-t{pOjaUpL?!z75a*e-m?dC?Gf&1JLMDb!y-Ynn+6=wSuJ>Ur)XC0MUP`$#0VX_L zIuiMywnu+v8CVPMRwCfP2d^}GvpT*to88wr2d1a!#3@(%1q`i!Uw6wi6=s*7+%{KE zwz2lP3YDCo#DeafuaU6$a59CgwTG=wd8wn92U!Vsm*a-R+>{qM6^C^}l--~wKYND^ znvIb3lE&1f8nBtdm-wAQQkI6T$3nMOYW1vnzbrSmczbiTNnct?95o(KNk?B$k*wx^ zDCf&y+#t?%e;*FEyF4`QZ^)_4%WATwSr#mjjg-gk-(?exj$PkVb}xC~nJn`u&pb`A zJSUG2<(xD$Vvl9CFsjEPyoZ+9!cCzK^UE>q3Bsr8;kk184}RqIdJmg-ilmVWv>!fk z328}Da{=OY-wCqf$1z=^5o=tyW5w%p-r_jyKdy#`2PQ5ZXioR0_)9E(c^=sFk*80} z9FOuKxlTYE`Q|PgBlkVtI&E<|S1aWtr`?E?8x+vp_M*qZ)^R}x-oH*<$^3l(Bi@o{ z#wHy$@3528FF*G0m)?7s^zi(ph}X&%JG~p?gHgDqZ?sptQ8~c|A1{1DtAwlitF@3O z<@DJdugNmM%^shvY1HJM{D^tn{~A9OsKA$~xGUm=-zgf6S)pY*8Z7ZRhKD_{r+=l8 zhpFbFid;?}7mDn7t6;@^nQ_Sx*nwg|J5ibz8^Ybx-E?Z02NA}j1b2y#7MKFp3jide zBus&-@nAN_q-crEb;_yQ7Zyv}#?397(_((lU*CW82}IrgOLvGcl;B;fo7h57t0*OJ z?n=1^Ne%y{$;(WWWV|V^JcGK|b5FHQ^EsDp6?`Z3=XpZH|1GZzP@uj<Tpm?g`Ue(y8OS{D{wcEP=-XDU!(kn|6llq!O&V$dGw+WlxZo7`_<3xdu zlM_TP19q(14W4-6x_0)^wVTc+zT=Gmmc;8m%bpz#wIj)P;e;`jFr{lGzetFB z@+}Pi+*U^NS@9Vz(KSGBU3HK`!FBaXA&w2sSIO=8SJ~W$R+TZ{6dKk;?VWtQ4ubez z%5%nu$u#*9f0${ILi(>DJ_fcAnwHHT#HNpMWW#&}rD(-G81-&h8?4)xX-nI^0OS$s zD-q@~4{-hJJ*~C#a8^E6Na?(?Mj5=yTiL-f4jG@K{>6m`5kPJPcfN^w*<7qB;FvSG zIAMZcJGmApENZXW#f{g~5+th+oD()Km!E1r4Du6mExK{Hu77;yU0u{e(SK%kGrx*l<`X{JI05{rcn9{L0|F(D?Ohdy`z}H4WBCl zzrth-Q0e^edSJ2;JAq#Zb86yRpfn1Iw^}+|GNQHUYRNBlaFcj{podf67|-#5A5jOi zbcela5#`7fVG0GSI$gz++ISv`#fpf$VgbhmJf8WU$0&Fa6Qm{kgD65L_;;cw_0|Ex zdw$-36*uIDWk$6e8AhNG9^>yqVaIwz-mx&bRLL{^8h8Fp#W;_;{u&MYlIqYEx?F}{ zenta#T?I)0C5XRreoQ!2Bj=#;BuR1jETP*ju#t@+zUH-&#CC!slgM*w@RU;)E|Ho6 zi^%>xX6YDx&Qp(IM?N;nV}R#HwsVk6YtD&KEZ-O4`b13glx4(6{4)j8?vl_pZ%9{P zbJ?o?ID*d!8}baR%|Z2d5}rKv@P5zNog=eDyc7DlhpjKje4T(`HfL$XMh#o2i$TA? z*-fK94U4j3#mhIOCv8BjINL7w8IEYZNK(O=Wf+B;ERr>xQUv^q#tRT{TYccTpxaKhIUe zqExPL{~@7|1e0zbZ&6mo|B>We%18U5Ix9!4q`#4r;>%WUe)eOj=p+u9pLu6z8yRxV z^LU!U_07aB^s-u&WuK}MFOKcWlO-5fGUnFCaE8FZSgg3eH5&ck*^5|&w^4w+!H;jI znT4!shFY3RRP8~+SML8FZSb>(g~f!EYn&HQpX@61hbM_@Ss#Z5XJdK<$Nco}8&~(m z(!E+VEs@xac&meSf@Ska0EorzJjU7`33g=~wghiF{{ zJ3aJMBO(qML&%C+%9doGTHi3)x1rWS zqGg9|&nws~%pDfq9Q@*LIr(uha?V!#nJ3?MdE&-@WEi#&a1{Ig?lJy>a}l>}4D;X5 z1eos2;Wyt!ja`tFjhn?$f2k9ecUWo;HUPew9ESchnr-EWOxu7Ro4;9(z85d6U)sHK zJ#NUZVz*((v@{BpJR+Z@s2Z=z!=;GN%Y$Qh`CwZ4Y%`6F>hc)c;t2Ws#h#Gv+N@6k zdKO(LUC;HN_~i~HKYqOe@+hLm)?-g2(iYcbB|qXDIv-bb*zpaFy2)_x^2}{JD`Ooy zE3_lYl7S~d>(Ibfswpg8}i9OdZvUkHieA7l>@2gAU@>hk-l(f$`B2( zQMCPm1ja^Pf}K=XqxGEP^%t9rQO^0kMay5tmoM5)fk%_%$m`fASV{iL`=&FVN`4~S zh}Hqq;0=5~7u@edwA7z&;h+hVBHp>3OTx0{^3#Zy;+B(NGy|quF=mljRYo7|TwnLB z(YltEzPp5E$V05pv*>6Vv}gT0Z;X8!Q?MY#+d_X8+m(+5?ipB~WY0`cCy;K%>frQt zbM#kJle-Q4zftfi^BWkS7JMigAXScT&Cs4EirTp6DNy!**n8`!sM~LEnC?)K?nYXW zPC*(33F(j&X^@U#L_nn*q@|?0V}_Dukdy`iX@TKj%5;{BD2mI?r0~fA6yv zYZh~jjMw*y9iRQ#d*gjWtyk+0EHBOR`b!9la$?L)MW*=Kv@_}^Xx%fEcKbCT46iF3 z8#iSUQ~9OBXCBmZ^bQN+eHYvW6 zfp?@W4&fq=1-ITwFxNyJ6RpCu!~s)Xv+SeHJ<%$WDZ>o6V4XeY@0Xrto1;VcsNp_Q z9)VBI-`O;5VCeR*$_NU`nvr^BaZDO$3}y0!OB%K(P4Yo)3M9R*0a+u9rlbh;-d1{v zt@Z~X4F%WwK3Wt2rDS;vC;7E_a&p?ypkQbr{rjYC$gu))XXQR49qGd>_#Zs!r8YQE zx=@TQN9$>=6DP$}joICu5dB{Vpqfo9*EPdrnZ;~ydzyW^3>O)gEjJt;=uUL?9Psh& zi*>J_GS?N4*YU>>tw$uNm9mbBt{5!8&5FTLhT=@}n5+aBXZ=Sl0K*ocX89_Gs_BrjNZ}Io*+s^2AS0rt{=0 zbVITD+v>);pMwo1KpS}TuZYJTf>wHuLr1i+ZK*m}-Kb7QnyyARxopWf<=`QLV!r;d zxy(HZ6bxVD!-8f3(Viezm7Wy7WMgNk(wZl_=%{~-fGiBWU!M}Vc3Jz!H697PajPj? zUakMulXB2f?vOVix7A|u9)vwq(9|yvm|{ZowqtA?(FwK%KVMV0;*eedbBZPbYd7N! zshzpR7N?U9Y(^lHdnKkn=LWO?*3GTF?Q}(XKwJacCAf?hVyc2|4*n>RJw&9i%MuKJ z&>c)*Y1DO8?MBJ#{M%Qo1^}MeYDq)tKiFB;lxR_t5wdxM8aDk9hxMV=2C$!7#8_dqytI$5*RzC?$t+7ML32ft<(I-e$<+CfgN>LZI>F)#u6>=`CiH^+fbko&wpkt@_Wc(&j0+m z%9lQrke<8ENc${1^s^olvYnnhxa?pzn6IH`Js%`Q=9$^*L+Z>q-&TO``Us3Rb|PRw zSNKG8sV8H+$raPyw5(*3OG{kGT!dQb?%uMa1ClF)z&mc{PnJ$vux$6IP)wQkp$=aC zlLhHEvtgMv?rYCys{$+cma@71&*`3Y2LnBwSiR33;}d0y5+B7I53+A0`)%N`bb{y%RaH!e|@1)iLy4VVRHqidY?x-G!39zP3-Io{?4E=Galheiaua1;vXfWjq{y zq-~JG!;!FT{G7qb&c^vp#EK#@c1kSiW!@{a>_H*MFZ+*8)2%l~M`~Uzd(8&ALiWUc zWUoMg`7Qmz-DZea!Tnc?Kr_CIp=m>oGgv@d%q2c)lm0K;1#Be`Y;a$gAWZIG5dmPT zhM#1U9{6JZ?FRL~!T7ZPk>r}VFz5e;UaF zqh$YS{LYT}(@#IzokYNN7U26+O!42%?==I|?tlE`z?amZ+}T{<^kFrYKTYC~Gh+b$ z0dwE2vEd5a$%8-O*Z+39z_wuhyXXAF`2XFO|6gqjn1Fd~quzPEX_m6) z?RE`8X#y$WfoDi!T;85Ww4Gm4*I2B6A!z@ql2ByO4kD(`$~~(QxI3Mqvp8e2E7q-f zZFGM)tT+sZ<3p9IX~%j6avM}NK{uDd4_z9&>uGt~UrdmN7d(DjDxK*s)JV7K8_i_DEU3C+iQT}>@a|+S zwoXjYpE2>)X_>v2eqGgeWyWV%V{PBEi=*dyc}n>b{`I?+JgZ>zUos4;#6Yd`2W<&F z>NN%tjVQzN`PI{d_D$Z~Ub$ByegxQBH?mCt?!Gm&p6%4{>?x3n%kx zed#V9D&bBrXWzoMYslseB*o0<+i~!=*gkJ!srUi|HW5voXum{{nC*dNtoJ@&`{4RU zcZ@V6wTN@gb)2;+3+2bI@7BKgR~UulCT|T(T(H~JP5f^(yq3RD%lgv(cutX?@x3Oh zP6BR;I4-k_mnGKH9BMwnXjS zgqz%~_GRa9+uNl{DuDPz_m&~%Sw1^rLmq*B#x{FRN5wVIuFS3<`+P8&UG>`~mcHm3 za$i{i>u&h|Z4ur3j6#SZ=kbdB4>oUxF2<;MZ78?-QFf%R4JjIyHRP%T!XPc$wop*o zhIpwN;k>xxNQr>GZeA^XUgJ3H9^wD1nSA4fuOnIKc`;{-nP0Umsu%o*IDHjm)NTJv zWGKEsZ6l1jA}PT4WKaSg#9$tPh)N1YJHnzxp%V_suM=-AU;&~Yjmy58$csS-r0sAz zL^h|X<)S0p2EG(n^JePJ(8O}lvkY<1mbV=jsFWhk@1|rdR$Pu%Q)VzXrYo24P5654 zFF7Hwk;46>?dDS4caBVc43v`Rvzm!XMgVP2|LtXZkam+RV{zb{a@`~%q{r8Rh+ehi ze#Bq)+uYcYM%pW*}sTnjzFP#>uzAs%IxSYn~lCYILoj^?h@$&~Fd-Yko z4@_#D7yWjJZq6j6_;mV~12zh;+G&=kY8d*w6Eiza?W0JWeUTyXk9wU?ViIl59I2)2e z=hDH;CwY-L5_$8&E{st@y6vP}>)yUzHir{2%Y!{`xBZLW zi*yE<7l83)$)Z*G_K}!QR8)sf?~{k}4l@-Axgf%XA->jU?(6+=CCORvHkZL=ddQVx z3j|pbaCNp>NCD3?L%+vwOut%4T`592UI|Ybzv}j8ZPo0gQ$hAHLCV~3JiaTCrv@N& zYzVpz?=z3MzUd!>=!uJT;>rqMA_%y_qq1IsE3)nx1X<*H3?W*UAjyV8`#Z z{MdgGgt5?%&rx<|&AkSCZ5gV9Zu~lP-c(yE6nK^<7xOwz56>0`&lQKEWjaIV$8)Nl zrWOm<-+ohrz-WfASHO`% z)-leE*OKqPHJEeOor=%IL|b!DA3qo(qrhBO+=8AFnw!$HiO)u%-GEZTPStNFuJ?_uU3TH0-uA>qwdW02LlH>rEofXjW3* zJqS^M^@OGio6V4)`-3yN=vAY<(383PIH18PgJT1y9@o``>*xxK{Cnu`K-|R1910AL z7h|a9wZH=`^EhkYx~uiki2aVhtNo=eSri%a8atSCs{vJKXwNe~qCW6NZ-K|%VRpk9 zKX(3EcCqA9G;&Nl^A37J?W_Alfv|b9e*%-8grU_EVFvX_dB+ugLnB5!aF0gtZ+rbc zTddzhhPQOz{s3JA<~s()iU74>Q^##C#8e}^v_f>RWWjH>S+5u~R2t3pH=j{kk8Q|P zF!qLdH`dukd<5S^j&(ZUQx(eJ^9+>v4j;_jW((Y9NrN?|5UIQphhLiUA%HPF4!PxP zD1#Qa(1}fNe7l_0;$Y&Q@NBP^e2O^U*b|+jr(m~`I$jRCm8aTu6e75HD>i8y1Wk(a z%8E)CZZ5k6@~u%&DX2R)km2-a)pG|B*oo`|10fR`po!YkWL{IY80qVrhuj9xcsp2{ zgQE0TtBn#pG}hnY^lc7<1u-%Rb>zjdgYd~iwSs5yGAWm~abXGOU?<#0^9hjz(BVq1 z`$eXAYVJ66S<;2QVz{{8Vb*!%^OPrUa*-1~9ZpppT4c6>fQ3r3^P<18sS}0$~ zrK0%TrtxH1*G=$d=zV6Tf#x@_QPL!P8`b5fO7yI+&i8qZOPq8mi#MZKhV;wLXwRiP zW10Ea2NH}q^-Er7u@-KRWAf9@*4fu`o7DY?oP-AK)X8VDp$)lbV<@j|-T$z$DCuW+ z@#fADvYe&6|C66c%i=4qhtH_4jiUNT_6c71t; z_6M<@5r857b;zYD*?F$6`q9oxmk__jENWA1sDAM1h4j}0OX_}@%`NasrL`xK-m z%SSJD#-+~tLtgg9v#OPUqM9n57YC}~{Qe3ws#QvIXiSsC5p-Xbqpmesm2Oz}mg!H> zlwk+@3?~77Y*i|W{nbAHWL>8>^$<^XT|1k>OXi1`{rpJ6J_MLUHrZ6H7-5m3nihcz z>;P&0IS`h^LIWCCP0u2i9x!wk@Iv+;==nej+}FuEk%^Bc_5#0MPD=T|O=cd(cog+d_H(~Ei_g)h5o!p0Cy zKH^uA%F)l>VPmvpQE;w&wx+ZQI(QXy^D65h+KJNqEiydYdg1*?q$BPTZiBN3lbP?C zXHtUkMzdX(ZbkEY#puP5j;4%@85x6)sn2ooru7FZRSkW!T_7p7>9DeJuvVpIQtj(K z4MigQ$J$H-$I2IvCJ_}Kmw3L2qzd<$#S68o2tNK0EQ;*aju1v5Rt`O4j7|1F2~BC) za@R)O5SAMkHAr5ak|5Vk_pWol6*Rs*CP=G#rU6(5_Vd$%3{mIpOmh@BH0T<~vLCx>j@c^O7rl^z6-@kFwx4 z(_T2{Fy0h*>QkD&AysauvwieycYHsJu8{lPyHe-Hx#MrUm6s_Gapw8W<=Xu`^Dn~T z11aJOUcH}g)n|3TYBB2MH`h?Q(dqxi->kkLtWEXFky{9*`99|Y2)p(s zbG6Rw3(q5NP@cta#2q9^D6O22&P!|uE;B9%TxC{=u3|=_FKwN_ zu@F80!e%s9X!gD$n_nAKps}g@W5?X!Bl}tS>998m2s@RZZf#m5*AX~SVQXO1j z_a%?hEG0$c7s%Y#SLnXKbFa!kY01L;e9dbN3sdnrUUTCI*Ehw{(X0qroP8Gh z^-g?YZwzoNR1Zv=onAV+B9!}Fm53@awLa^;*r4qSB$l+~?QboudE~SG{xpkSn>y9Y z@?L5qikj7WL~(T@=<%z;G4)dY%D68I5vl2jl~2`gacmE|Z9s=D)BV&cNCv6T*K%1} zs6!YQ(}8hJ3d1%+$EW)j?UI*2bbcM7e7b<5q#*q-_B3i|f}!GF`&K{5Y93~hWHh3X zhgLqO`aKWAe`+%-kXDIFgf$u^X?SfsUyZnfZDm%X}mKj&J)&rx&w zbIfKdZdw+f%Tm>A=0H9M^6Cscf|r!aC>Y~Rx5|+iHJo>d^OOW^cl7NY!oBT`Oa1IW zqXmZXD5Tbg#|_!8t!oi&SpuF9$cx16Tp)S6?2C zcGu8O?auj-3&ktUWwZS&18fq5AKi0n1$;c}Pv5`LeB$&n2oOc_(IRH-^>3z?GsQ?~ zq^`$qg5YRrIbx3gReL~jWrOK3o(HG*u-Ou#oifA<9SBy&|7S2mKmv6%XJpU&!#+TK zRK^F_4Kw6jtKl2|y7N``FgR`nu4pZJAhqAwr>HpzU00#+J^K*%w^FPqm{$peQb(XSN zca8^^tTpLBpZDL6#LnUZYhJs|*zkW6loJTP+g=o=(to!re;5Z3KL^%4?S`m2@ZZkn zZ{YF2z5-+}V0%&T8DjpQ99gfsHUGa?3c%_9KfY4zjNr^xuYu@H3Bj`@jVtd$$K~p^ zuk~(S`Hz4;jP5|%$O}%h7RS)*M)#?Y;|ot3J$9#^1gxersx|~pJYv)4$;dcJqA9`6 zCuIH8PV+Id2%j$r>Cizv2Z4>oV0V5GX>UqIn|h|S{Q3So$b|3owWJ;800)_91Q}lv zpoDwllYIZaLjtIGSL9tfl<7;p;yp<%-!lezDmz zpcC^oS}PQEYxT8Ew5tJg`s!*0f)RF~w{5mIg@t`W7A53oLJ@Me>iyz7S_-<|djZSi ze#l<6lz|n-?(5!nyF1_eo`VrvF8jl)F`tKLFB)(IuDvYV=w6Jpf z6l4m(&l3uk+cg^k4x^Ix3spfagW+7KlUBs<)l~JbT|$tFiiA6=Sqse*dPr-Yw^JMY zRxNdz65hBJS<4lk>Igy3iMXZ4OHT*F)bA(ds98YBielekDQkUezE132#bF_CE_yuOnli_ z?VIj5BY;Z$`s{3CF~`b;Kd(aOa51D)HpW{fK0m+99q2brx5o8z8#qV;cPtR=uvVz> zKTZ)DL-6doBu>59+ELL(P<@ezL)n3{$Y9GEb^hDp_~S0F#r1=Emt;$Y2!kv$dJu2F z&gSIj8b<^kVk?2qru|p>i}ue8?rTB<9_J>A5;$><%WY6vUTyNy&Up=s?GW2UAuN&p zt=IPF`rUl5aw_-v5fSBsg=Xu*7C-t*)dqBIqW&h_gnZ|HBlq3{@`2X&O9N`7MoIaK zUDwdetEk516g9pM*%~{|#F>_h(?&{(D5}YXbmuJ#HwaSNAPgxro~XN7eB81qW>+*A z5mIEn#gs1W61RCfqc>M?&zfTLHftkI(z7LC0YVo2GUZ@gZ&yuO`7j&r0tN4& zFpAoMW{0wLPkh-eqRh0>Z1;fcgq%126Y1a(;f%?)zL{c63UbiX4(bBpSle^2a7S#X zEVBR*m4Mse69gQhH-25m<~}l&GGI&J%Cl2(n}wZSWrrc@nhm+N4|e=fr9vfAQUvR% ze?U*~a^btsGf?9VlcubkhsgxZ%zd&0E|0Hvo5ADe-G*rG3P(x@yyS%6t3~EEQ#(TE zZ3{uWQMUc}4?1$S%C6U*z{g$zU6fnJ3lL&e-w?=PjVwuD>&wVAVR_;i&}KO;t9eIT zGBXW6i=rQWPX165R}lfi;e8^nskU?5Ij6sEK+&0tYo*Nh&DX16Slv~V2HmfCM3i&o zFmJYoGg}Yth!;)Z-og#P6@PUeCJ7G;vXPO?SS`)^8_5N&h&TgYE z38K8m@9U)LStH16T~Mss@VgZw8{nZ5--I(FiOw`0X)!=}Kqt9_?SPC{ovkS-=q63+ zf27A{oV2-ffMIP1KNeu)lMP zkx;1}hs?~mE9?Y$gS}qvEuA>~<}@6>TCZJX10ETE zX$m+-hdzA^`p}oQba0O{G(i0`&_1yl$3JA0#d*4!q4B_LL| z07l2g45aPv9Zcftr@hqR-n-%r^%EJBk%eAi6SEjMI$sRYt0{M9gCVz%x)r07X(hZ$ zzB>r|zg)Z$a~-?4Pae&0RUl}bQL)+7bNS(WRxM?%?GU*+i2bTw)4$W#smy%+^TX%P zOj6pPxgc&6wr62S;Ir_MsNt~+t(#}wGIMp}LkCJgbYVkvUr?Tx0UAPZ8LT#0s{urG z;}a4Vx4WG4rf>}r?S4g_7r7s)kV~%nNw(@VC6)9CbbE94#?pHy&BLi;N>_y&>ve@u zx}(lj-sHzj#+Jfq{B9*IZ*Gyz?DP#6;<~qE17`-vk$XO!qDuKlqx=q!vKiv&)ear7 z)5kjV26cO}JYT`Ig4^VGV3eh_?4X;)_1955diB|$(wli)&-_j5UxS#-nipBm-cU~f zVm6u(mG`l$qv#3uKQzq-uEk1+YQd*)x2E6kJVr!OaMRzSkbKf7Z*ZP3-0eE3m2V0T z{jw91wOF9tdo0c`q54_Ko<)Bb>aZ4ewjMQO_C!Gp>u~v|D#pZjw)fa}CPlsZ5UG@H zMC)JA%YmKavvqmw(a_d84-d3PAB9OGW3O{7R&rSsgZt14#=BfmJ=bQ=k~SU3h_g`c zSy=PiF0fEr1pJJY)Jt(nYiXxpdrHaOrs17Uj|>{kd&WhM12 zJQAhF(Hu_ZaN)q`=*9oku-%llRz zQRluW#AEE`#C{75jz>W+YnQpv7B>Uco6ScEkKL}p(97vemOp^Ll`yL4`LTr z!65l&i#i)pne{$K6Jg_536s~;a;AZ-C-HEBPE)3A%4TtzYbvM^_b z4?+~rq@r@X+PXNUycZy3N#%H^;0N2A7kE?V=Oe1n0=axg$w_#`;}Pt2hPZYR^Aa_x8t(*U6U6|tj?itsQKSi1R+K)8Y(WmWr+CULi|A=erIgmgi$+C z?0Ncb+lwh|1SlTkJNh?m`kwwcDr^FsfncfQMBm78;^x4cwp+{HXNWcxI(=rGrr|IF z{BIs9#U|ly_-F*3zlKe7j!$fS)lUfN#pWW}W66e2B3#(!HqjzC22;37m*4&Vu+c4L zSCLiL6xv0)JJp(P{02|JZua=L%skk;;MviaSfjz&XnTQ=ujbvq zBb;s&Gk6K-UGHQZ1IgHJXcy8!=#7G`IqeYBWVuI4+lejtE|=zKfyHsAn~%Ee53l$| zWkHq3DVybR4B}V0mzQw|s00;bO7irVfU)>`**^HzG`x-CbaUCZ?r1E~+eoMTj98julKyyl1fQy(IEBE z7K}PbHW{^CS;A4`@rF>Z!e8)ZUvzfarw#mJDfS&vB|AfnOljt-ru|JVOvj9ar}rbC zijecSQaSbP74Eh4pxIh6%Ho0+r}TetpNsp^re;DcRxS01AyRsFZco2sgan^a3oA40 zF<6F)NTSOY6WNH^VlS30kyX{9Cf>Z>EYWj#OBVQ|oj%ktn2yxag1nYH1l=}gG(%_x zDG%{n%#tLfgRRF+_VgE zR#-LaXp6QSy)x)Sp#1Kxx$maGt4hf=86Jo%yGD(~4i*S%zyS8jNWOyE0!^X2E~r-$F<0(Xo8wrHUdjxRXt@66i?K( z6+YwV9#pCg?aV7@WM*`enQ6#{x?Tuqw|~zf?$_|q>-5Ly&Zvbs-q-%t%UNsTh+>s7 zUZ9#v@a`fQ@K%2FvGc%`GUtRemYyO)gJ;`1mk5~#wzQTXPKNk!FnPn5tg=vYw&Xvs zd8mFKeq#*_q7FY1EUqTA=8~k9RUw889{4}pUdnN-qN&9T1TocP!_B1h7S1kz z3!c5_UtTiQ7hdrAIsVvzewkR(fpP#g730*j*&Nh9r)^3|@@cDa9S9Yq6&_PEYD>f} z)w#)^QE3fFrX1dgmiBI+J4qSu2GD%kKM^=2;!h(#vQV_s77=l6IM(G`u^Jp~cLFe3A( z%M@hB@ayYk!jL4A+ZKglmny$`75qdPPR7DJufP}gm&awhb@dzPqiLjGR&=Zi-@5#k zu_^0tW#f(Dib3&> za(*W>+4!i;1x@?X&rZE?Nh19siDmKq-MmUHnA(Rzh;O}bJ8sSiv6(3}HFWv3 z52->x>Cha^z&8t;0THWiSg2TicOs)D>#*Dk4?wW1*<4@i{xrx=+q_GK|L;Hj+8C-^ zD^ghP1xo0B)Q4KNzlNt!f3eW&p3Q-uE@Wd}+wwkpoQLOp z`Pd@d)vs1j7{$~~iV%p)8XW8f7#}_wMwd?-Bz0n={te}k!6JT4LV}k7wfKhpCSzp* zhE>?xLR^W%8;qNrh(qd{trSURCAe%N9*JY(>Ri0|IB^?t^H?{z)RG$SWgj$)+*4^E zE%A5Ll7SrewN)|uq{qDyng!d|;7$*UK#Pyje_0S<&o9oyKi-3J6FYg!jM`rw(kyx| z=kcl@nyoH1*-*t=EHUVctwjpl(>Gb}-5y*DHGo$RWhXUMyA$=XEVREbU30zQ8$Wr` z1XRz0&8k1%9R0XywREj;FxFrnXBe9XKAyD>`_>$ryop{zJ=h8Q7D>_s4f|4-KI(4T z(Ir?XmlvDr{vV1C%9KXHMgD$)O58BRGG3XjB(HT6F=l{F}Wt9)ltFo}QE z!61L1Y0)Ded+2db^SyE@ebet|2ThyzjpJFz_<0sQ-oa5GUc`cVtyyIB?eck=nzgii z#hLS!_ho}9x|@R?MkkREE9;b$@^ zRm_T8ikfnmQT-U~@~9&Z46i7icgmG)KWRu`$d%Ic;jP9%ODyF-pUuPK;5Sl$)*973 zGX@25Gru$Eh8eD$$qI|dkbV`+ScpsvQ-lh>Y;eSNpE#qvcEVfwIH~yzLVQDru|Ui{ z6S`}fM}s~eak^sF6Nc^ zkuDkBY6$v* z+Dxg=kgd_HVDegFQydzc3ayTe5$D+wJm)6-Q2g&Jo=q<*K8yzRIQo(29ENwbD{C`S zkG%zAl$I73R-SgmjXz%J;j1X%&0p3N?##HoP-^R4;hKKmcZ>s8jnN{Nw-2S1HjsHZ zNIYyA;xPZSq(@>zr1rkxNfis!v~kg~wu;X0S`4Jue{x9;HxO@QvapcW71gkV$US=Z z)M(QNMNnPDX(@wKB08eP4GpmP(@M0&iF0tEk>}Kt8m@K0v*6U?-z)9jb7kk{my!!t zId)rM)bS})Gv>oK3&UV7^zqKkkD(yfG-k1HHH5sfkE%Upp6k3-u)Mz?D0)rjTum8y zAo=49vc^Vrpp;LFgd*^Vbe1Ux;M%}#e>qJHcbiJczeNMClfF)q|40A(2S^1!=^WkA zFPKB=7Gb&(?AJdXE`iNb9chMZis16jC79 zi>dvhpSbZ?>c?vUZgs+?Xgu&}+GLp&6NoooX%w7nX6jXVqm=Isw&&*n7f}xTgw4Bi zFX|NPJ~}hdVf*F6+mYtdcGIC-VeoWi5&M{EhIBif_CO5Zk@{jn$-)XMa$m?XEP&HQ z^k?g*Q-EiK(EL!4-%uQhPyTaFQd1AS&8+*e zV4smkWb~Yg%A@ov){!#C-(8Cu9zDgbvEp!E`boW%MV#cgev;zs52sqFPWZ{Jofpiy z&9B`KX+r52=Byrl$k-9WTf%)O-{c<*rzN;|$SrE6KoR;LKkhPob+G3FeFY>8G=t&M zUA-&O=XYnXeqZM%0P2KjxGewC3|NiSjL8(T^**6YG;unK?Z>5&$LCC2}*&gT?fRHbO6J{$5@)-&m z{(Bz`ue)z4ordTvEZ#ymqc=y5zYgK*R_~>5V6uvu)OF#b*-kg3V3IUlR|xKA{#-7= zbf(_bCU6~+aJp2tU-;U=n)D*{$jg%?(lhn`FFq6NU441R(0d1FMwa5pl{r^fu8BMf zb6I+;VfG^oSNPQ;qew5=_EoLJg|mahi;ZA@-5G>u?n{kIy2iSmN&v!fIrUj)-8~^i zcJy{Fo5#7ZYX^4vhQu6oSfmk3I=p62)J?Ug{HRcO@hY@M;oGnSh9o`LRG@3Yr;Ys| zH(+;4_<_36v!_r;`L>u&=d`1}>6gq&s4dPsb04nG8aW@`@4J^N`(!lLlvX9|eQQeG zPSklo&eRPEF>=7ZaA37~Zb8fl=qFcPqC9?%jl*jKn$sdm5rrZWaqI>4ZsvZG7GAHz zV>FKY&lQy}3uU7%iYfZPeT&)|0Eb{ZCcEZeaKGJcGoXNW?u#b7b*ihERqrQ?#vN$n zi^q`_OU06akV0}<0qa46)0AJXc;EX!H}P8$W=3F6N8G%Ic~~G__i=_voai0uZ?AV5 zPN;Ni{e%S7XFIII^EVK^A|#5a{4K7_!^#`XQg+|xhEBII*mfm07IKK`#MM8MX$V*q zD#H~=c8dB_t4YdZKENMleXPUc$f%AaibT)(kVR9~_F_@TVfKPDP^7;}#4m_pD_qeJ z$Y!ptkR-sfNECnjLs{_hnGs7fReFlaw2OB_Gw zefbMh&VE0z9^3TVo_@=|MD78k9yGc+hPII9x?I+#GL9m$u%TRas4IA=-TE2F+ zM*~1gs9l%jmrV_q;9Rss34Ai17gk~5ml`2{p6f47k5}5f$~_lnlg170^4NESjixx0 z*1w$0hf3WsY@ly|nnAbkkKI_QVjdo(0IAYJVP@0`_M3#ih8SDv>H=q0zy&8?}a_0A+PnvJz ziZ&R}FioAdH7F};^IA6;P62Pz)_8e!i2D&bCEWp*Z{ zZ_dWoF+PH&@ObMM+V0(C?P*Gf=ulXLSN1oOly|ILgZs3U+voP3bm*X3SQW4C=Zq2e zG0k0}!0v;JnZCd_&C`GGML!q0w7YHf=*B@^B42N_{4l2W|)=QpA@|;(`5Lmd0I1hj( zVA(1yyTQ6kIAl0B8|+E7bLmTm7`M61?s z^IcQ}O-DBZlN^?RT@EY>`8)#uWEuA-3s{0$Q9NDNgEojmHBj#KZ`xo&{G*t5Yxx-d17Wo{r zr#)2}s+lpwm;a&chckqNna};L+o8?vF{l5i1t5DXApLzXe77~n?uJqUT}u|hf`n**1#!7t*KszC>SN>_4=h`Q=zDKJI~Kh=Q(`7{bouqtq#Wh!eMN zoBxDs?<&OQJYmlK-D;?DYPc4X(0JfC6P&gw@qW`2yJ( z(R5(Z`b0G`zo}YVK%W}mmYMnYW4bvUHnq1)~}-6o;n55fPGnMb>FAJwTF#U(#j@^|Ny8}}<%R(s)A zj1Z_voY}2=Ppm1lT@O2k8Iz!{;mxevVkn(7N~D*i>|@cDw$%e0*Cm6P5V+tdxV}Ta zd4#Znq&8Wob=ikWlon5bq6Pyy10BNLPF%5cI(bn*#<T3{qrzu%}?%ZIb=iOt3SUtQtnX&UEab2 zbOSUrIvgg-JcV8QFb9XPzYQ)OIKnp>beF|6X6R2dfn=}Dw)tHv-;K~BIU$}mGzY9r z*yr9cr_^NIeqV&Qa0K!dkhGol28kHYU!#SC?4NH9y;}Qc+Vyh`G(d0i%6_~*D2jrJ zBYXal(vc?kIv0!le)e}%-f%12*D^uXVOr?n)yagz`F+D}CDOFz}n08sq2!z+3m}ME3JR%CTe&ugP8Z6 z{o$Abs=_}&sG4l27 zzbSEb&zoviE9MKZody4rU;k~3>9;AP63NQV^2534x5Xj~%zk}TXUNzC;)+e54;NTj zti8jx=fZ&U+cs=#x}$)5nMG2W(V!+r9jADQ6Uo&xiXJFueLo$nSP^crYTI00I@$=|*Vi7eKyFiINC zQy|b#Phy>HmmGI7$lq1*bf*KD0yG`~`_FYa?&LiSkR>UF^Zr+t!GD2wz*cGuf+IOn z4fPTjzvlerNC#7d8fW_Q z>Xp{S#OP)^J-#V2l=ee@yyn+`$#l3ps8{1NAYfdW0~iIb2s@*(67s&AN7QGEdT01u z;o2QiIRI^j1cpSon@r=PCzx)Trn;Xz5KRLJVJp@ht!)h?p8&l3`Pcd0|B@mBRZ&}c z_A)lJP9&6E5g>{<|IP4J6R{-&N)+_Jzau%WYP?Fg3;N;SksPhY8M5AE2rz=jkZ%ND z$UUe0s-Nyqv9O`9ENPW$*)(G1smIPT|CV#p(Y>>+fC}+A#`mBy<|! zKjM~t3V1J}&9*rCw+!X208}K5T`PK6EajZ5WM%dy8#t1U-}90HkaeKpDLTniZUC{2 zPfSdVwHY@u?^m__u+ZX}KAbAV9+p9wZUYdZUR6phq($o+ch|F1aH!)lUF*@9@W|Cf@Y5QHnS)pjRfj-Eu8)lFN8>gEKK)e` z?zWD}BL3&`>+56R?yrtMT$r{%&{Zr{N8bh>YNbheBdetW-209BjvFHeh?&3%WNzdHm|&5~@6ak$!Rr9G12mFh8(R(K29K)LRQFWcjQA#ul#O1Ot;$iOZKWJUgzP^d$c6RF%l1ql!n$ z$u*R){cf2nc=ae@COb(X7!j zEtxJ0`m{crChxVk@CY7oSRHVwU=CJ;$FqJ?)7E~)B?!2tzC|;A$BV>}{yERxe6vg) zR$kucL6;jMh7H#;+ohg?C;Krqz9(eGhP9trdi*`8FN0>xmv*O=kaR~e^$5V9P6VP4 zbk(D2JHp<<4S{=2D#r)6v#rEXj*@He8iXbPDUQOYoF}MHWw~>h&e0ksCUi96j&?3Z z-pN<~c4AVn=yq~Rf4C{C!u=Qd9K!i3e7P9$u}etEa(ddxgPL>-I(=5@{ku1r-Gp0i{|f(!l}(A_!6g1O%i8kSZmFB3KaVpmYn;2~rXuBmq%r(mSCl zolpc4N(i|d&w0P|=<}ZMd+r!_+;Q(1=MNa!BRgx)J=d(iHP@QW?d`Sx>oZp{Qj2@R z>ly?v;w8-*cy;vFF`&O@*?^X_vvbBvnamwk9Z=2kSHcEMJ4-cnnb9sDr+LtMV)tWL zN6U`@AtJJTwxx5_FNs^i{Ho&O0Y3j-Cx6q<5+H6Xglc?S3Q(eLDIBb9 zc6iXe3WqeQ-lX+(V2P1a=zyM{9$5v(9DDoAJNdKIKWWNhKk!@H=IHiL6(_}%Y&%%F z?8_R;nfcMeCxIQRZ&v&R$MrHVTcVamv=n@a6AKn`l?HsLsV`mx@A#iC3#=gyIxsr* z2pg7EPjZ`~lyq>fm5f?iT5=G0oaX?X9Rtfn(B1_Vw=%MyoLDeTN%*03Sl$D10eL>< z7G>xD_kps6ep_S`W*Kqgu!8k?=R@iy3X`LWUdkyxxEI!}!SaY4VdB#pnzJ^)#eDgD z=K=fVVQ!w3mfS4F1zOKifY61hnbCuHiC$TcmK2c>JubjNRLDJF{(n9|&7?aV5D_JjT z22fr@yVdqgL#wfX*?=Z>O1@#l3vx&EXRUGV)(!9ZF_*D_J+PM%^k1#6)sy(#m*1C!w^Ombai#(DExd9 zNbJv-c1VZ_hNyJ0gc7uaUo|mqTPMGsitEyA_1aO%Q1;6-&B&wrXNV+YIZ0~|H`@m4-<(PfRDqZ^eA zq);i`TBKdE`O=seg4S5+`Pt@4)mqm=IqtGSmK_zQOAR+VIe*1GFI2%6Uf;S??SfSH zMUq~HFS#;NbF%`q$YU<-=($U91k$2XW5;>i1$}#~v5yvafXu%6#e~fAH>w*Q?V5%PZ!{Oe;L+sRZTJ z^@JV2&%;W0D2p%pi#L>DNNUpN=H(|^-= zzFOjfgilsrp!c{+ZGasAJm~>Kheygx*d4cyqd;UG#@sJInxGrV9dh{zhfC}!1il=3 zCn$BB>K5ruC@FO98Kcl-D2iU_J>@cdN1=%WW^Y)*wjYfoyd`0C!)F>8jkE)6{dpyfHRM2kyL zSHiZ{Q`|D(XIvY~GWTYWOz*gqcvDKHZUP36a20TumV4~mjrJ)`f&4EXGBcng%`?6T z39Us0pQtsY;XKDPFfoWqTi<7v_w}LXLEJjC{WYt}s_2%d*AK@kx>I6I zSN*@gcei-R%7Gwn-+-@BjY=r9)62nqvK8gJWE|D;n}g@tv&NR`6srlY!^<_BHbX9R z%WIv=XdV1%ienv_*#xyC#hQ+UW+6?w_8{%@>-Pra#G!FQ{j#iX$X>Np2{|i4Md#DT zvUd{_maBi@*BVt%{ovvdH-+_R+Nva3`kgq(J{pKQSQP2#G3$@s9XEl_bFr3GFI?Up zfqW`R1SGqPTJvMOt%4ZU12{VwLz3DG0pZYX4tYf5jh>Tpy~s}3}QXVcGg zU^h~iy=?Nv2F3%RlGUrO+bblRXQ*Bv?D827t-3Lhvo`hy9Jw5t&z* za$(btCh@t=T74oq!a8noMOk|8iRu!`b@5o~;KS_o&Iza=Ybn41F3T1S=tlaxI@jEk$o`9uxQFKr4KXGDt z*G$>hN;rrVty5MuFds}gln^L=rk)E&!C ztJ63qt_C!m|Brs>|M5vcg@IVmS-n2SjO*wh)e$=eyq$3uLUT0US zv%KqIPi%E(?TiZUgFOQ^^1$4}^k({q9?u1tM4H+&f znj%XpLws(NCcY{Y^vbVq z6@4;K^+#gD`(lOVK{T4xRk!a(RLyO2&7c)Bq|(b6{@h!oI2}BAM}C`OAdB#ngK-J( zKDQEAth%sz-4ffstjb3VWcwB{+t`2UjJ{>vPQ;@0c9%+msan5EI0N-b($aU8s6#%; zpPWxloxif%agIFnxugos8S7fXr4w*S?a#f5zpGeyiHIL-tXj^ z8j^xKS1 zBbPA?uvz-C)$g;r(?|W6zYI*VVV!#xXR#jO<1=gLo!azD!LW^|OClT&_|F5~`mH6Rr_|^=aHz_B|ean~3GVjbaa!BIvi( z+X?cRcB@^yQ6_k-VQeM41El3@YS~nz%txE4kYWh>jQDzs6Gh#Mru(9aqvw+R)uGVg6~N7qYBx_JQY7G7@qSf*En4pa`&A7 zGOHls;`Qjekpmp4YWBLDlAk0jaU=C@M#t0?w`3iUtBWA?TdK0uqlNgUFp-%G3rz>> zYrtehoBrH>to5dFP9p!}EAdzSx5>d5x(8}?TgzpGXhRYQKeZkt489Yx9CAD$B$Ojr zFMK>%wf1(!&RY_~K7bVPey=_rbr!C{I_^i>Uj4zOI&TLIi_rDJ-g|xq)FAfrS zazX783k5T7xcKR(j0TIoB2rJjSm2t!VyBP#(CCN=bp0GT*+A~S>**FpP8D|!-GgtA zxx8~Gx(`5r)2dV#pEAj6x74JMbdDz+r7CitlO7t^!6ZG7ZYsM5Wu0uaNis(8;odnS z(W^_YP;yP#T^r|uo*fn5hmBl0t`*@T8@|fqom2jE>Yjc>WEm6~0bz>~y5E}vWiVgUVD%{zc+D0C z6FfRtVGQN*bvcpttxF@8S(x`AM$!;A+w-!cfhd+BlG+WsXGYrZGEoy^`&2yt+?5|%jv$UC@B$`{)Z1DgbH^gva)~>I0)16B>9;2 z*sXwhgZCP<5M4LY_(zIO3sRztp%r`Xdto;@DU1Y7AaLp1#9!!OTv*an>4!WTytp}= z>63b}!|+Qa3T;2BNZ>+IpL>TAe{n%2n zhL%-XZE_sKxtNghwGV!Dh6XQ540KpNT7F%E!0r|xGzTGl_g%b5D#I@eKjfnZ(oQfW zkY=~R+mdhUH6G{V*pVyiwXUBfC?7qOU)*m#DH~4@*c}1=w7RcZH78dZ{M;Q62FAIfws|;sCuwjhdSfyN3eI`srm@B z3Lr_y5zE=~v5lRUB$o2aJhdlF4OQg=LPi-!gejNDTKZ*&V9ZvCb!9)OCvP{o*iZEk z%pgmlxEM%xFINEFTm^A6qt^V>Qpl=2nK(ogLlx3Id zuLxyGomLDmX^rU#G=HM^I-qZ*+rG7dCt^k-+fIEsrV_y#*%pIBc1GG<30%7LIlAON z+U}|JP}BK-<3-g)MHn*EH>ijDhT_nXF}tJA8&8O)%l6sZO7Iio#%TIXxHfExEEMyN z1KCfOw1)bSU3a9)ez z=VhVckv^}T6|;<=oK9HkakI7E4QePy%p@pT+7=1J zv^~T65lQz_nj?F&Hb_U!Dg&YeC2-U+&AW2EC4%B+XTya0dlM~=72_P?1{Hf}h8-&M zqKkh3JM03g&nz!tSKN-+X06~dgp+rtBWW?+?u?j6nxn3td3a&gBU{X=Aye2CL3rdL z6f0i?WfL%u!tg6MiKmA0%ns~qdd&*BQ@=8wCI^&u6^L&yq(B`ooCRU>ui z{L~*ESFWS&@^QK_B>EXt&-a;SKOVa|c|4_-`^8#0fLhD7r1Uw#AO+~FgKtdOQ;x2# z{?Myd>DYnu7K>e#-N7z6gv|ajj+_hyQLCqZkBI3)uO7L2YbBKW;ovCa0#9vUuHa$C z8{G5CSgHO(Pq7VFZ;Up1PLSOTn!T*#5n|!hAR(zg z?EMfpvpe(w-@DCB+|Ib%JiO(I&sMMZHsQ-`HS8^Q7`nphVq!d$lI`n*TpH&(=~|$d zA-l1}>y8&;~nB>zu9zDx#yn5r4oJh5kl@$-K6=<;JcVZ(CL|_g)O;pf9Qec zF_@vNO$FQtSr<8upkUcY&x`d9fIEuaSe>){ejZy}rTQ^MEp9lPEJC-@QB!A@^!R%q z*!LekG(n}VDtd#d%?PDC8l#j_%zf|i$-wHs9LN{nzBM5I&NKOplQ<$AT)e5nfA+dx zO!{Yih%K29^`v&MA9MLpXNZ2+dWZb=&O3S>klMje{!(HvUV7?{yx@`pZ6C2D8smrUP^vZS5-NZsdDD~TYK|WJO z*S9O|j~^Z4DV?M($pq{{k%<)*_v8*?h0gTLnsJc*G+!nntTv&WR^@>Mj3%E3Dwfoi z4lEB8_R~DR-IXc!RUdUE-CRs~OJa{xdDl1iG(fUKphoC{&-};`%&B1#LOYsSdZc)i zjQ+%xAz8f(b*tdyZFBB;gTd6Cg+~^ReO{mjzQ-#oI$e9hwxp`={?N$SSW?kN^}#bx zp9lp*ZaSg|6msB16*q{I@W25T(HA7Gei79Is@G6L$5Zh9QZhtFw-P%hPQTcHceA>q zE%^~8rd9H=k?lKlP=VG_J(cw^9q+5O^q$c#WeF_a+$po0~rgpK4#iK{BSN$=d!IH?uePnS^ zgYWniw^|E?ZXdjT(L6V)a4d5beu*Cf0*Z-wFku+UBUGrj5atu=(+(Y*`GCD>Zy{xI zjStO9Zy?=dkg}P><^U7S98AidOo(>N*7aKaltQS*&MxKd$o91wA^4o2lhedZLd=2$ zfaxr$PDZ(;qgB3zIj1R@1&Tj4gO{z}rI%%K$wD6&PJLhdB;bryPvbSIHV!zOYrk5STpE@i7(da*nsf-rVznLoADApZZxKj9G zfZ&yy*!e*hSR2V}`0Uoqwf8z;*zbzP*;Z9#t@k@0^-;21-5&R2+UZgsU!|Y@D_~*L zJS`0N=!U=;LxIZ8FN8R++P^tweTUFhwcA@7%U@u{FG6e{4m zK_9p5ocbqW@U62;VtE8Nj5S($t2JMQG3u>~+a1pkqf`0H%eD7d9}QhX$j|fo&xXg0 zK4r(ZXD)Kt6AN~B?j+OMpkGw&Er_{{NE>+c4s*=At3@A+ka*#%Qv6Ej*^Sh1@)z<& z&CZ_J9Ou8aYHFEX0yiQp)ee|zlfI?xJ;fhg4rDu}&a_?PVBEca+<6@~o&HS9iiDGk^ z={HqkHUsnHp}@&J{ev%0ouPxt&ncQz0{$fS6t&&hOxOrQI=ryT!GR-bBx`oah|YLPCt`7Sa>(1F^_ zvmIjG+G^naS23F?s%v{iHNm|d*MJ5ZiwX)cnpVsD)F$0}l<`e41h`pUpu35#UzYc7 z4(hH^YQv?@gA5#lI}FZzc>6`g!{#pl$VmXW?|=XAF2gO3r(q4Ezadh8q$>Zqynho& zWlZH0PBH!;2L1KO{~7tOy#F6={QsXD<~Ph5|E4Gl605C6{-m);HbA{YQyC6!EL!$_ ze}H+hhaAd0c`~xjSVWD6?#sA*5>QCCbETCe1e^EZy>PX6EU^Nec_YL_|K;C-Pfy|W zxXx!(uWy`ZL)6+p8weRp=}~%y`_Pa*DRRdp&8PcpgZ=}1{+DY1%QS@!atf&b&&dDDod3Jr2<7G+__u7%?gIBuPigfR$QW2z zjm=TqBo%LKYsa^rM#rIG=5i^*U56Ps92w|$R5^F>8js$5WMxQwLr3MOLazjwb-zE( z4NOX;p5&ALWrW{tIwUJZh|znwBD%tn6AvXMCM9Y0x5%7ha)nDl&0o|#VD6+n>!iW@ zb)H6Z4myM9Xmd2wFpm`5xG1>lOP#U*u%q;!Lo$6X2#G$$(a}urYqhlX!=E9-6b|#U zVCofCqiNDi2riFFz%9S1E5f1VOJ**pg{iG8Nma1?69GGTOh7$bT|WPJvHx$R@&D^X zXMbeCrIOQ*rXis+41XaF| zknrWz4^JAKdeA@xfMUg1wiSUkO5>Gs%5RR373jtUG7dR39bQXOB~E9$>%S6V`W&m~ zW|cV-Ksf*U(htr2q9UOF>8Ly@QMLJ*$SIS>yE9W$O}y?niSUDLiOQSDYwm8RK>c+y z{mJ0!pOK4pG0Kruq$qBQsy<8$R2XO14M=P zWF0W)$O7K(r~jC|GlQ%N5HhqKvkUw^z6gzG&>#}@+;$0ZZrf5`CQaZ0>{)7nPrT)h zJNoXB#7FBX;7p1=33Z$P-6T(f?GL-@RHf>L+Uw}g%XvpaADmg8O3-WF9H$B7?rz%+ zJ#182Ysaj#(lP_Swyj)#G@A#jkV=6DQYGXT)O49acjX>T9|UOP^;l&H)!@vgxg4D_ z_q#?j5#t1NIC{|U;17!ATx0lt4IsAaYTK!Ezo|s0H|W5Qt61RDuwig!`6nT!#Kh#* zrIV#J&tavZ5<}kYMJGyYtoo?M0TJCSDLw2Zvl&SXAq*SwgZiG}xp(#j5IJ(8+1lqq zXzQ>K*t092(mx*p9KU3jJC-p$<`R=&+m5)_u3+=Z+coQqPPymQOA(bMeh1)8dPVPK zJZ+k8=8?X(x(@|*9m_ER|OXpJ6j*N880UhIMxGWNg|Qe5kvC&xRk zt0#hG7(-v<+v7Jjq_=yTMYIhJ;y0FhASx%W+=+)gg3^HLZ+aIKo$Q`_+iUu;!YZ(< zJ!|j0SXtRrGDNfb`j+c11JE<&^@s;mhNSXdN+ft%3EIE<2 zT~(9o$d7f&lnn$a_{2wgP}(`b)oHmxyajjg5-mE@Oph7nXxEN@69`dLX868NYhR$h zarX~NXggABz0*;if>EpO^X@SGA!GVVz+LGPDxH=KpQ;H|iS?(s{4Xchqhd6ShC)40*N++FA(Ek|=v;uRM@Us+0%&xS3+UC3kDO%UHi-9?S!! zjIJdgI#Gosbl4~i*G;2w65t4-5)~Jyo7J1zN1G?skgSLMs4tUlw)VH|gxycMx;nuo z($u<{d1fdz?;}Y|P=bblad=QaUM;2~YU_ zuhn?ZpM?!q#BGkz^sR!YNxB76+_GSZvs(CEHr)xsRU{yIa$+f*Y?)pgpFd02(;b11HT;{da8Ew<>er>)$;Z+2Y&ID z7@GV&l;jml?}{*cF)zDNVcx{~^Ypp==v)GE^U1RKO7Kp?U@=GJ@`&YExD3RqiXuJV zS&~HYUm&YK(YTbCmltcVu%l*Dy!fH@WkIXk1t0)oPTy!ea!y3VL>Nwu-d$_G?es>F z@l?=isOOlx15mM65G4OF{t&;)?Ze7b)&n2%4Rf1K5^(*GLj|XL4eula%_>^lFiU40 z_NFV^oY!px*^|oKl`L6USf+Y=I^vth?>rPd=4zY+>H4~XfOIv#&A$W~;Q^&PLR9Gb zRrZRrI6ykTF9vy4Fi9uUF@xBIw3L72CLQQZ=;0+u^#slQ^a<;3zxC- zT$ANCVr@M0a8hwzQ6fB&IFX6pt@rj{DbE_|+eQf-CnLJne7dnZm`>Sbi%y(wYv+2+ zH*t$(iN@$t;<6{}$pqA>b%LAaaLcvRLUrU6=)JQUD5ulr<1Q4~weL>iQPeyEjDIDJfGFInM+tHvjOrh45`8qHwy89haa%gFis22o^4p*UXhzCPkSa}Ir@?9Z+R`WmR&zNJ>plPK!s`=G@!e;&`e(SG zjwaU$V@T@vuZ8J&T(#SJd0!PTVZOu#?(T`7?$Bg^IK362X2_D5lF|xs$1j4`D9hz& z*MZ7HVgZ`->Pi^C<(pag$SshsOU%2MO4r&|SBOt_DQv4e55t>j!-jc*e74jCr_| zTJXz3dg38?$8B>hJ-vP#>XpyS5K87@3jb{k`=Hg=pzT6&Xp)$3*IJKzP~pI(fm2O~ zP#-l5$2Bf)QQF*Fss_?cx)?mN&^3_HGW*Wej<{h>dAaS(R)SU0L3@oBtEP#9m?c-CKRZ9+`guH@VL*c z^4bh!rKw_myje#Izd)9Cth=)_rk$`y-D%$X32l-~z$Y~)4X6eCh1v#SlB|(LC!9wamlIYFM5veT@I`l9cWiq^o=t_sNTC%y=%sHU$`ZX@ldRr4f<4G@J0UM zy{Mp#FH-U8BiDfXS=!)rzvP}6LeDWBtvm5|O{i8S1J*QM_UMObrmCMSn%abK-srv5 zD2*0XH-zS4)kV$=7cQhEAkY|M5e5A?l%dVzD$pjv=jh|3CVTy2FH3MPnSz~mAQS2j z&uS9t?%LOpl?bOus2T#8$j~z2#0E( z3Vv0)v%njlkR}+;$Q}<^Qf1R)z{VHEL%$aJ31KdP-DkJXV{&Lw`)JWgeL(Q(FXgj@ zUgf&gNjpmf(pKh5Oiv+QaAf5K3bA42&3lg6r zmD#gg-_!E~hcS7I8I}mL4#v`e2zD~fb*;AYP0n)Rhh`1_Te^MZp;AVhge81Y(WlBw zu9wQ0q+iuZmCtBxx>?Fq;Jx+)Ra5JbHa-Za}enK|vx* z#sO#d099gWwu2WH2L4!QPMU=5b*z7&5mtQ~XYp*xkGRtS=RD>QBLY4ds(b1fsFL+! znA(y?;vdP(AP5u(S_iD*jS+}}!P~=5V;+93QN0IULWC~#N(;Hv^##A;a4Z46E`~%= zZ=`njV7o4#EsjY(WeqG#k4tuccr$JMWVAmd|3T<2I6D>EW+i?zy99W)Pm*(u=LSO8 zp(E240z?L=4k^tiYS&o-T%q%A?~~uu;aAr9`Ayv(Wkn1_ylgsL>B9R&v*V0lh?){B zV71nVW(PT+x&tm?ONDld+Ajvws`UphJN{9dx<6*8D}y=8^#Zx!F>v)cjgAVf&3O0{ zm~Ig8Yqc;F7FZ4a~>}qV@=foF%jVFq66 zsT{!lM~XjANr9Lc08&C3Vt}bS4gf{jmlB8m5n`(=4zTM;7M*X*zoGokr>ogA0djV{ zg?Qk<><_twC$N(ZvyY;dQ&9o!&ej#&VGak%PahzZ2d0||BLce^AN}(Y0?R6+TSGg z&-vabBn%ifI^>(=ztFLEa8uJ(ZEAJI_>(4h-I zIH>w}1^YYM(E@-}3ksC8{u4R?hA@PFP&q*4|KGjae@a8B1_vP3oUudTe?o_@1VD#j z7CreNX_w%>b}=;({}E{WVPCuQJLo9?NV_=qwTr>d_aAAO;l6hDI)qUFk#;fdYZvG; z<{zkwT6X@vb}d|~+xth_#Q+%9QuaTIE=^*ZnwfzKN#wi+Bu8zO;qn#8K)wJKn(^v9 zU{oP-bkuQU?=nYGKzj4kEGhVdqZg9SlpX^-PU(MY&|f4@I#Laf!=AL2`(@Myzwa-H zBL`IGXbWF%+5iCUSHP~eZB2Db0}E|$L8K)b%F#COROkcII*Eg+^UvOx{bj7bsPpeK zDgAyw*UZnR`KYS^Z8W2Sng5uXhBTc*L}mNv;Lr!C}U{%Wd|Iegg5QyE{^+NcI|?sM0`t& zww6Y>BtuHYij-VeLw8DzdU!6GtKhP0FR>;)Wbd1`YLP9 zZlN~F3~I_p(rtg>vEky=gXg`(;t-KMI?0?N%*r6%A{kpv=TF^fCaNy9)?O*l|q7OJ&iKjY! ze_FrK;dO&q>r>Uw3WZ!?^AGq-gt&wD3`;2UBa&u!27q`}7IvSlN zQ7r`*n=bT}ZnxK>+eyfNbCHtYYexrP<^pf{N9^hJYNnNc4!xm!JkP_vvP$U4(J7s- z-gyIQSZc-Gpt1RB8H4U8hljY@KHS+KvK;&Yxn-)dv*gq)X_})RtjesGaKO7?S5jVW zmRK$4-b^<(%%FRyldl^pmMUFpg)>vK!dXdMp`7}(g2z{G>vCUh>1&YMk1;|cd2V$c zmY+lN;(VQN(1j|nig%b>&Xyhh3m9ar!wmQ>Ex_BL?vZ+VZ9#@Jv#oO-Caqyb-z5U< zlofMZ;ult>cb(S>NK&_FC%oYagA9Z)j2VJaybk@4wIMoZFmtfA1Q1@GEfv zp>x=yK3IpVv+Ka_wtiTM&Z*MJ^c2zl7IS$@S1t;hWjYZpn@tdLiq$=GC?4Yz_rf+< zBwz9JE}2^?zc;~%DLR+;I>$Sq>Z=!& z)az#CtoY7xK6>J4k8dvbRgX``Z#fue&wro zwS&!x;B8CK5Z35whEN8!Et8rlI<}DNI($iwCS^AMptv}*-?o4BvIs?UvAK4NF3`Kf zTlg-e!V_Y}xC4Q0lPG;ZEG`T>V%mrbrvXK%a8a zx;_{(^W5x%THllg$Eqxv&p8=a9&%T?Sz1U?;0H49hcz_Bp0-KSUYb#XxL~XDCV+d)G0lfIpnZ z9_-rM@;1nfvT-1u*5(lHGe~3vcpLjH3Ys9|U?B+3*&bGO!cXaq_3hQ%IC5a57B9*~ zX0*q8Cev|<|KvJY3v+$F*xg+aV4JNq1Td1sAgH&UAtHoE?+mZJ0okRn@O zSsetbEC9Oy>tE<2j#Ac7%Uuwb7uzbiW~bb$W#>L#60_fHxnF(TT|QfI`0~D(LNwGU zY3URZt$I_eC-lH}{VjTR{c-SWTr)-_Dw9%%u=D~b7}$50yFGILXEK)Z8$f_z{Z zb-#Mf)mP+vCaVX1g+dY|PFa&l}o{JjwKy?e>6UOoJYHy3AG zUSpu|;KBoVH^-(c`x3Vd+Q{}nzx{oU6L^t_7dq494}8)K5fV2g{F*`up;)ghA;#SW z%kh)GH}R2$(wX+ECo8-8S+^LU(T^2_Yo{gWkkg{#b4YtYfmBbZr35n5)wK5$?4E&^ zc?sFe1=L6H*i7HBTu3KJOa-sLP`0@Sa2WMe?UARNM>L*w*#$i%xjf(|Ruvt>vw^m| z0LjgvUhcoRQr$N(s$B<}8L*iS<7D!VAgBy}UE#*U9o5kmQ|tcxt%g1Bz8(K5p-%gG zZz?b3Zw{0PB%6rvIw7Ae?BT+T=FQWJv}Y11tpgf^yKuJOmlH`kd#+V}OnWX_b>X1-k0@)xvIWTSDi8 zF$QmzVAaKyhr*lkN=lc z{`wTU#rdjC0ICrK=_9fa4qr7QwCrPX(urz6E9}eMHss7Rz(nd4T^f2K`6g0aA30uR zo~;73n1AGr=IT5LB1m>6ZE~@=!Z~))#TK8`W4$L9Dj>&Ase39gem51RGwQRIFR114 zS;S8KQK_wuZw{vg?CZws1m&A!8I;Ef9_omU!cGDBMSi{!_L=ZUc(ETX3mU@_V8Ub6 zz_(m$UP7oGze-sMn!mN7EGxSR<}S~?19co)A6FJvE#4X~_$3D%dAUF;L}GY%_0RnI zr$DJ4EPrH&%yI!a$?|c=cenp5q@q}-vTdKmbu%o960Hf9Xhg|J{}mLy1kY z`_T4lAFu$&ejVlct3~^|@K=)~zy~I}3{zB^<9FY#70;{|7jHVJz1}ybZO59b5%Kqm zq%df4;An4h^^z+q(n{y@ zs&H{nJbRcYDyDQT_2EP&<{XGuS#4lwFHAu!YF)^kn2;7Y=(-O|H1U?NB!_hesm{6I z^qmOd%ZTLN-59Szmlb9&k7|_Q7(o-%@^dWr~eI=jOL1)SQ-xh1SBUH7TKb=Sm7I}6@ zI7lY>kpytXKl=&ka@E#2{w=gnJ=smgwYtx&QsrLfl3kMMRf3 zrhpS%beOHgNtrj%$7g5JJCKmei>>3=s*CvHjj3gp`rP??LjPA#q4J9IiYInpSy*X~ zpJ64u>p+pdc*#wTCtbq1GQuGiu6;Az+~ArnZn64nZ9+}^p$w%9vIUfBy@nb6?ZG*gNKnOjSi# zK4LMf?HXjeuWGiQ{3c1k76YmB4ZZz}mPs66oLe>eRg}$i1AqhtK!<=m9KS0!0MAoE zXsgyslz;zsmk53d% zNC_%}!@`JNKJO2W`68;r!XJs1Qmx%Vxy%p*-4UI?oT)cq>t z&)YYE<{_rPuh;^M4eliqcSu-3VpYXPkccMaXFkX;dC zd88hIxj$Yqf%ITzQ1Ddas71HB9%(=g=(d^^B^t?%7Wxb;6dDe=WQ@TW3R*CbBXc#Y zOU0)%_SPRO%DeT_!L8=ejV@UxS<&Yv;u(u{<;@JrrUJEwf=%uK@S*^qCGtat=P=ee zJVd{YOtOSqw;f%|?z}R4^6)z_r6G6qeQ3e-o+HS3j5go@A=lzfGrf?#kmowjY)cDC zY=jBCf(p{uTM1#DDxmTw6L3|gGY%a-r%MSjZbaBh8-q`T{e`NbqFV87o9%(iRjp%2 zy4HvHJxI0&eXE@PpWXzJKRcqHKgpn!z_MFW$lysr|Tr5qe6LSyr^{HO+d5y;=mHQ5=z`R30nf<_? zUf(Zq%nPsUo#yw?`Bc*GXR=EXygn3ku>ui$e4=D(ptxX~xoTF@ObyN4Vm@xauEi`d zwKln_<#hAeh|15i&1F+`czEHONsB}lRD4m#bBadBLxwAoct-s;VsHCgCnIq1GR6RD?d_dAX2Yte08hZ|8 z@}cyWD3J`ZAu;$2UM8gQ>RN}_c*~(iz`NALo!n+zq4q4MmOIfU(N+_Eg{`~&gL_NR z`M2|^*!{J;yo9=MeLa>^+zcC*&>zWLX&CXGf`X!SAn9Iini-9GAPa5=B`a~Uw{h|?12;Dl@CeNkgL&iyB<&!GqMDt~SVxADia`)@AKuYLAcUcg#f%H7ht zQ{d9Y)-v>Q@Dgc09aZm{xsUnvfwgCg<61}&HYTes8_l4f%c2Oj0HH*eJaML`#dJwO z6QWD@m+d*P>+Vj60lmgijeN`>8(!d?tNp{E602 zC4*@F1gfe2_)P5t4kG|0c7>7aoBd`UDolfrx^L{?F;k$1t|iM(sDnhE`#L$gsGX6X z%H}SYy?A}G=yb^m$u5D4;=&Hp=8t^IQt(hY!{oEh;ZoRAW6bv_sykn<5r$t7#%QJ{MzVu=lip$d zeETrc$`9>Y71st`OrLrBmxK5TSYpevr;d#vANYsp)XwQ9-d z09+h`76^*@<$z^a#G&|X(W+$!kSV*P;apc1vzr*lxkFVUwI$_!>UAnXm=}q+w;Srb z_EE`OPB-&+OYgs}CAIb#SXkKsoOsN*tLWezXES;@p;_mvGj{A{_&GXN_j=eOV1tKo z;g?Z%lf%_xEWy82=f+XM-JfL>`z0KI`S+Bap0eZPe9w5vFu;kS%)hnYej2aOhtzYUY{N=T8mwjU{GtGIN`pk#e)4%~ldKqK(>1{YV0SF(TF+P;N-dbU&n@eJS3 zD=#qTGja`XaP0j+@GYL<0J%>gi$|%tqv6rlvS}9YRn%+j$+4}G<0AQ567~ya598Al zg#~wZwR{)L@FP24zLcT#DWj`ei;IH=3A>BgPu|}+!eo{CGmnt6*&5GW!16>y@Nu$v zcVeXpLZZX$l=-(q`|<$nSvkklOZ;T;XR!EP+uHsPQHZIiB^qn{ypTtTF-lF4Ik6Z; zafeus>K(k*t7Z}grpgRYi7?!A^aS7_I;@{Po~O9rym)evj=OL30k;Zz)-_1%SPRA) ztBek)o%KzllQRe{*3nLEms8FoEldq$S*y$8Z%4rts*R?%<#*-=Y}7BBq`+)jiH*tg z?~DAJa-R+1nRGki2X^p}I?_`(50rw2z z4W>TmNyYl0+TBA~cQMHf6m+BWdP(WBC8aZIah?_=$p^?v=u?1IH{RR)ZT0x4p#Hs+ zL2MSAwz$2=B|cKMuZ70baCbh?jP;Q~B)x#RO&X?;;8YV3W)RiUu2sVzgmA1A6m>uM zxqYw}fdx;?JgLhPKaYe*gXh9;&;ehD2AZ62zS*~Xm&yAXz8$<1?!3EXnGHpw)et+e z*GGoG4~qd(a&5`EBH?HJf!c0KJ%PbaKCIgv&BLWrA&i!ubxTJiPUmNd`j1{Vm3LgpSdl~#7uXS{dOOUD0;y|)aCtJ~IvaY+b}5G(|DC%Dr{f(Ca_ zaCdh?ASAeShoA`%+@)~|K^hP4jXN~%&|j1HoKx@KJLl~CRox%=$EjM?wN^KDuQleF z&wPf=F&s?BaAApR-6N?chzznDUmbO*S>ZYE6au#2N@Z0JtKegy>_1DWG{8cW3?e&$ zHr50W=$RC>&J)5HmJDnj4mZx?>LQtJ*w2O~ncD>P$0y8H<(W%3gE7Fp?&%nCqc~^+ zQZeHx!^68qU;7iJTTtAz@`#t1OvDifD3p@-!#d~rE1_~3BN2s;_#j-Et4I$U41yOZ*ssiEIn>%_bJ1@cc#*&*C2rVA`qjlU`tCankXf%p zZCX6gO$E$nN6NyUabfj`jvZ9i(+o`9%IKUkd*s22dWvpAaI}fQDqWKA+HLmp%T*&M1JH zxzzSQR&aH9UASpwTO;~f3FE`i1IY!zfhg9DpGEVJd2T;@F4wl+(}f;nKYMW5T+m4b zcm8plYC!uLxx;EODy7SB7o@c-z#6Vy!uKOHbnT3{iM;(Le2s;kefM*y$L|S^WzU4~ z2S0CJIz0T>{aD*naT!nibPPI|UyLtls-q#JZ8OJBsblKZEHA{JK;8}MN-@WtBZEgAZ@4I3pqca=Pqe*1kLP$J3oCdFEH^75j zAJCKQWdSM?>0ajr9t`wyeW-U-q#Z$J$Q2JDmy3bS21iA9vGmBA`kiO_@X&aSIZux1 zwwaP02RM;tl_z)`|G#8ZYcnh7x%5SqN*t@FF{`mvEw>Rp&rDO+B1LM{^0NxuI{F74ND2CM{I5L zpyu8XA+}vM>tYG`(VV9-`dOoNQOp9y&8z!4^??cDw~%?JeUI9|jg9|!!KXyIYA&>& z6xXxlV2&z4tu=dlZ?oDG$nDQ4D{Qd+T5Na^Wa}x7eHI0teOO0*!;X_H=?~LJ;qL;} zvufz6vtUDJhUo0vTbjlms@Iz%v%@Brj4n->-$PY^A45Gx)^ptG1Hb`vHA7T-@XJ*7 zlTkXG3I{x!fGOsLxtiD*5>+}ON(mZzw-%2`EMU&!OjazS&7CO(yB1F8Q0TExOX+vw z#J-q7R;{FNsbkP{?*Wz}+#D-R?eEK2>-BE;*;%PJsj%t51n2)?U0qjs?wyfC_q}(( zB#n_nsEz$ppelzRL|1O?cyl5(-PkS3T|TNxZcZ;Ro_3n%e>mH%Zm7Lni&f|z%Q02< z$*4AQ5~)nAG0+gZeW{&#%xff>AFy_ z(_00q3qzYV8O|7HLYc`x^CCla0713S^Bv9|1s3;OU(QBf8=Zm(PZ*YEVGgUcIqxRPEzJu2R%Zk8nW-5bxJ=J$VrvIpzE*#|s zu~UsEJNqxZm0a$)*A5;}(V!H(JF(Ruo&r}RJ!-$Wf$@Y+} z4vRgtFPrH)HNNT-+kWd;4fzPvNm5K88~mAJ2iKq|KJ7(Y3$=YG+_a`IuDdizd?%Vu z-DIRSYmg8OR@u8T%--e4S~$bzuG=X$bwiL5#I&QqLL{sI*#m&cijjZin^4Tj2F>XR zTuHUB$h01okxg z5#M-aV{Kk7Q(DSxeUzrlmG!czAL3%ZxMC$Pp5#iV90U8_gdrEHY_MNA!9NlY024s3 zdBS;?5v2yi@}z-#Df8er(b(<89Rq>%0VTwm7n z&EsJ;-stJfz4Je1 zeRFLcMt$Bw85%^+hQ1k0ieDS0cTXb_oBr8alYaAyd-(e=Ua@QlXnmXfwb@`?@vC9k zgn}ywJYiSmOQ?L*TF*2ZFm5%It#TWaEAF9zpd)Wps2nEA4TD>LgdXyTA}kTW!u2mq zBO;t782T>XgYP;+>&-_E7(ZEZPAj?x#dO7v`QZ`G1s!iN(v?}K8&(Y6j__6aZBC#VThWia$WAo$ z8#d#1?Us;N__UhEO?-iufNIzhvft-POovw2GU(I@#R*skw{vdLv(y=@f{SJXV;!R? z7Do^7R|Ofu^4KqbB?SG$Yz&Sf8EY_p;HFK~msM&rB5v0ANVU#HqU`l29ne^j11-h` zOm|yL=-&2OM;ke#%D?YGQDY>7o@esT@qy`|*wqja8z9zINQ28cYfYo8UdyqZk~xcTL= z!w9B8UVt9{uGc4r`fC@vX4hKyaBQ+G`B!jrQE%^zO6c>kbCLk_-!9(#*QR~9l>QEW zmWb5xe}pnDUe*w5F$4JxPe?*dZS{ob^kg|_U^2S^1yt)*=BRX_zIM*}x6{`C5|5n* z!l_z^%H3gVt?%qTpnFexZ9K5jtlaE(Xm*<=PB=f0&cl}0!tO+ynM2SCf=uK(BI`$i zNCfb6>_hk=vLCF*QZhy|%a7QuJ{~zM{*>eVLl8oC9;0mrwm(FH`(75CM+qA1?+Hl> z6vfTOW+eT{WUSwtzq`3&Cv0E7JtIK`7bysKCr~&T*2VM_LTfnWIGi2EPvuL5gCH#tR0HR!V9>A+{Yg4?^y z<|?G5Z`;ck)D+|+1vymA^Gqn;P(0swJtWD`jAY8nrwZj|lGDYac|`P~8tX$+Qkc6} z&_>YN0Jqb0`n*olmC3w-$FTs96~}p+pQ8n2;e-M5861};6sUO?yF<%3$@yXhDaovl z^s{_0OPgxO?YCz1eB@AbK^?q}FGE!!WyZtPmr+E6a%U{(-L=zEzuTgWG~B%7J{iLM zCU4qxCzRZzLP}}Pl{KkiNB3wJ*FIU``#d99$H%ac$Tp{vy_s$5CfmQhI&)W|VdzMz zi3d462k7K3B%Y1-^9X%)x6cUti`YRAr4eVzf4|9Sq6t=T8kW96`MW_+$N{1`41UIA zGej4ke?9RwXWuXu*^yND^PH`a@qzjVGNv8n2R z0XDz5`at%Vket!{n@621ywm^GK!@rzew+6W?Q8XYoEupU=lRi}s9moT0Njjm$1&^T z7Gh(n&MTB9NwF19Uswz!&foo@cyPya?Fni5n%T1-0)reT2kG0X*y~bXx7_KT#dxa> z?&_={Y3%RN>-e%UBfZ!0JtbN6#w_0*kJN?AV80!(;MSd9oCBS?x!k#GPRVTfYv! zWnZl%`y#dMl$Kz}&$FH}F&)2FD-+sj8MuhhX1Qt_>XcaC z5*pV{pQ%(n3pg#v`P7^Bpd`_g;$x7ZN`wXK^W4@%kmJ(pwkFIhtkt5+&!xE6a)mZZ zdzRM?c^i8T>%?05mF66pI-^|o8Vi`i(qvLq>D?%!VfEtR%N0e7WnQHY)!diaIc7hu z!uLQ%R_}{k{ps=aY9rT+M$pF^ElAIIP*1t-h*!^hzoCo-O1NucT>NE>X#%V9_hSH1 z9zOx-B!&EGw1d%Z(A;r!XG}EYDy`aoOfu7El@8gYlitgTss+szI;)?zVs*y6rG*Vk zt-!N*8YrHPbc|wJb?ZX3;ciEP$@`J{yWyTr}UqvMYtfvyz$3(_^`;TqmqroD$ALt!YQ=V%uMQL7}fKaQgNc7SDMhM zj0GCJ)b@o&1?o7m_^=@irXP26*og)V4ZPp8Pa?#jk)b$g0aM5qfrF%muk*MELGM=7 z77cwjM3)ObkHPOIZ^W3t6otXi;D2?$mQf1EKzW?~R_6B<|HpPsat3I&pni^6e$KdE zvS{Inkp}MHswVZg$M9?MYD3Am(90&<{COQTE)3@YX-(tp_E@T$4^E{=1Z#Vgdq3^2C zKBgHmVa%5WufbkdZuP?pxm&9o-->X-f#149hp&jji~oV1>lNcaKH=}dtCm>=J|mX@ zWLux;kfz}UQY@f6e=l)H z&WW-fM=#8QgY56K+`xj^e*bO>`KTrr-I?4c#kL;@)qm}+XGz8+uU>C$oV|tPBr2i_ zGx8HTU6j{~;P@{3GNHUpo%c-yH@)yp0!K4sM%-F)TBO~&p{v2hp)?+%U}rZer4FPG zw%VT3m+yhD(z)03dG)`SM>_n6D5PeOL2CcnC=f9XN2o$J%9zbdGHShpDs9E0P}3Ud zi#}FxBV=0;4%ioQ(xSg56%w|JVP0E$(IzlTS#|w&IHFJ?=zFzSspL|u+i?D66KYyW zUhrn!-3f4znD~;45jujRoY7J;WDvqhx%%luuh*DesK^WO;IC(TigK>wH*j?8vG;T~ zL+nxgy2f&%3jc9;XDKk_(!{Nl! zU3u=W#rNlXL_f4;sc4%&!S18SZN-zsW$bjh4=jQ068lM`R%z6;IU#Y9tYH@Dn?h7J z7gd^1sRgJU6FL&$2;}g1OHliQNNw@ zJ&Hm%RgE(~AELn{!2}%Q4Ix|dW|4`(c<2BN%K9XjIiOuWie!5Wr_sdU(=pB)ZKMF- zH|vT_olS?7v?eDrO)PtaRgUr?&6Se7zwG!Nb}@Lhy=72{B(-PgEc!$yRsPZ*gGw&D z1Sjo=C#R1JVG4z3pbmarq4>RarQAPqR1EeX29wI+aj?|ga+BK8!ujm#LB**2$i=Q6 zcaI$cjP&WN809b75Iyav9G>({Y(9Xf=4b7a3CWC*OT1MG$iE6eR=9BsN3P95!a+aT z;2{~_;Uf9KkH*!3GB1IOhxR3e6mzqJpc@C5*GcSM(JvqCqyA|+t9O(1ZeM3S-v`}~ zz@ikswedO!b3%5Gzc4uaxsc0L9yoSlMx@s-kK&hbfC%q<37Jd^$&95h90|+ExQhhN zwXmGd2|DAp8h2Z^dz@_=DK66FoERogzrJ4~0VpnXNMu^S)#k62_V2GBJ^&oF`p5aG}Y=tcKuU8`#)20Qwo34G6bVPSln-f4B=a>MiQ_lXJn*?@K6N~S9H~=J?=DU88 z5ZILRmlq*DYcDxL6aoQ~D@c@2@96JW12k#?9tw77e9B+i{12~y8EREgl_dYE?{#dt zLAn_521Nv`%*c=C)MKg=ouO+T<9Jzy9Qoi&N+J?=7=0XdkR;^TSwyJCvi|uOaeZZn zS!)cWL06=f5=+!~NZ#nqr1*l04?k}p@sPB6MB8UD^m-sZ%`jZ8vv!m-?J8fY;z6+HEvLVVhnpaEv8jeGS9Ww7fGZ! zMEMLk`I@UQiW(6vzqwj7^bsO=Dx%imTQ% z**x=q0BT$?!VHKD=1M{Af*|IcX$W4!?dJc-C6l_VP9coCRg(Ts+wMN z5K*$5!fT_!;NRj&;0a;V+Ibkr$j_E&7BrBsVh8F;S;4fQMj}DU(Xo(-LM}fEb|OU? z4=y%kN?t=Sp!`eUjYRsb)joHRk)vvy+HXjFHbkhByxx#&+j3Zs<*ay8!`$$@_JH>&vi z;fhD*@%z(ov7%)SdnH}j)lw$Dgi|C3d69$I{gitLQ5n}ZBdc6*!dM`@=7UMbm6i&O z=&mp2E1J~QX@V>Z)M$#5*pui>7T<~>a7+Dhh!Q>P!$2%XFZ(zGG7w}-jBIVJrNni8 zm?M5vcJ%~eR+|P0S5mXZiF-M+(a%bH^fW%IAqt=wZ~G(}ti(d|<2&MUa|ltAA5IeA z;7`ExsQYS%K(DVSdM8j*%{P|FXW$|%vMLlcJ1NcS>38wVx+!WiQFwwfF`RzRtH#QCSg<|ES6Tg|h)0)xT(My4_ZA6RtN56u2z1h29* z7aoLC{a~r6~PS6eg7cwzr)?%&*hNsH4w$|Ns-(? z-|+wNWB>b0f64HFrT3p|{=XXW|CUBXzBJt!Oh?r1n_oMA(?L*#wXS$Szp<4~`=?wO z8u{%n1lo_4OG(dm$Gd0z*ZWge^b+JAd%xsLeP zf;a@YsV}{@xNoZbPlq~VNUDw2UNK}0hP5|GCk>f2>1-6E%)gIk{or$^9x57BU242L_z) zi!m_13KE#eJpbAeAwAJJnHO+-xLn2KJZv>#`3&o41kGpyV1_16Ge9;kXbYGaDY?MR z$bUQaGavI9(L$OkB6BgPe1G2+UE!uxbtPV+ldIkjMO-Y`=JS&E7 zTvV{>H~l;#36DI*&cSMYbyQ;W;F9;!VXq%E$ zbkB_cj{pC09??zg!9xTxworxIb9Ie&l$dB5gRehL$lLidhFgjQ4 zWZ_$@6@eXsA_Y6bW2;4%zT89$h{&IQIbCbPq)3KZ4%D)#LWjZw-(0USDkT`e>I^&) zjY+#(5v8Y2UmEDoI~O6^heo6cy8I|T#6!@BsA=aR3)V!q*&h>*w51t%rk`Rc?bkv;A`Aq^LX`Qk-TS8$z{CtljGS*aWdH;ebh!51ULZ0yzPAko<^?_!QHjfBdEm{Z5 zoA+mkP+nf|PK$#tf?oOITaV?p*w59;Cojfs;=ofzP6`>GB)?D)-j$I~6&!l8U`*XgUvJ{$5)5Exy1v6Fgg#!VRm6U`Os}|FW)Y+a>`$S0S`e*t8D-GReTB#MbgJ13{9MRalR+hgL14i( zLg}+0qT(!s#%`iC(P~EA3MURSW+S0bx-(G*0#4SH*GjtxT5R*Ont?g;f<+Nd-&jnm z!HfzA5iOQbsG5VwOl&anIw~e^?=+894M(bn=@qpG#)21gK9qE@Sz?dT1c%bXlPW1q0Q_a`t%Vr=m{YqeRnJ+D1tIT{Z+te zbdrvqX2r6_(Eu0`xalacsWjP<+@)1S9|m23$o! zA_;s4$xW%&?w0h$PyL7U)ll{7U6W5Xr)PUZzy8usHanm91JeiXMT1}0B3hkDvuG7k zJtXEx@Z0y()h^au8#JcyyCI!!3240Yl!J%7vuFckE>n2;ZKs@ZBXZX@%L40H`%)*9 zuHzDwkqDU1XKVe`X5FNyst#Y15H!0#!s*ASbO+3luX4=t%=lMb zeAATzCaJi6IS$2??4ofR!V0Cb!5%BoFtFUZZZh3h;&~n_*)~x!dy^bmF*K?o#u3DB zoCR|*3Bd{d`UHq^;opOFY403VztuHSDJNFNre8g^aBBHFwWoXt6O#W}+Ttg1ru=+E z&D!wSj!N%C^>)4<-mppwh19gV1|JOsJSMSeNOCZ1Ex_Nscq(;WJ0$n(+e;-I_{3~_ zNrsBp<>w@3BZWtgaC>^xOyo-q>&<79D9ukMY1f)L(&@ky@RKShqT)n&)sQH)1YA(B zWmuR-DTRN&_WG>A3hkD{|9n7h3kn87{Md%LWRq_lW4X8zyjyofw9n5Qn#^qSRN#$O7Q7jei{S!v#9 zP@D-LdQGdE4`lG@C>Xj{BsEqSKf@hUM-37B-rr^K-JQ#3`H{x|dY;6COsw-PcH7>+ zpVz3M>U=a$#cAW?Q)c_B5@*#iWAczE0kgGx{EbDL1qr@qH(aWiZ(r0k>Lja^G`)XY zG+(8n_p?h_gJ=O6BVWas@4YTdH`v)aP$riM2)a@1o))nCVORA9(m z`v{h`Kp>ZOTmRes4<%~NUkK>C&OUZMdVMIUZ8dfG^<#^#Fza5effKj#3!0;Qr(!5U z@DB=PMFKn@=6;n+hf zq!CjNG#Gzzit6U4TlWdqRL~%`qky-z?8_!$BPwN zfV-R8Ae~eZwAQxa;w>%D2Nc*;-Kr8jwGEzlpFF5nb0m;aFo4JY_VF8m@gakoy*y{Y3@Y5Aw89K{cA(mYGN58u`f;z6Mr`p+%BXH8l3)A|NiKq0#`rKVm+P+4o2*tr21j~+;YrDmjeR;VGY!qY~ zrY25v))hmh0@%QM9@vxRFAaAVmdjcPbd;bB(T!{BnG~AKYY0xi$AX`1U&cc(Sh%p8 zZ~>Bd?*n7fgl@R8GH~(d_OO+9p37PsLD)%Ts5u#Trz$j_8UN}R%^TAqeMpO4Ey1;D z*cC;(%iDswADsIbe-jz z60bVLto}=sR{A8_BELBqP`Mu^C$c;t8 zwKW8IJ8rShPiqE3(=YpYHBHWzW9-9=-V?&u7_A zm&lC9bqL}ddrzEKC8?ONVi3%PtoHB#KRDN5eaOW62!s2{f&qSEp^MGg?zBBN(Q1t@ z$S*XHku9>ua!+_?QcJUA#AC+mB_OlbBvWOzD-{;E5O9^+9n!~HX506*9M9^*1%e0$ zqrJt?3IX6e7yYAI_ZsQrDaQ5_-=n9Q%gt|4Gi5^*3dbNI&ay{gZzT{^b^^b+xK>kQ#xK^fY!9Fp${tJucL{Q_+R|$c7>thr3#?3bp%@= zJJw&mYL7zfo3O;8jiQuLvnt93!JBWs-3Xg~uMWDSZwDNYHW&+H6u#)I^}lp|VF`Pc zAhCi_X*zafZyjdM{45-D{vSyPTnx{Srnz|VFSNwo3odA`_owF>OJhu5?=uyreF1S8 zsk^TC+jMCT)%ra_PmZ@z}mw13~lEMwwGYJ-#qs zNO$6S-rf1Z`0ItddsOwx7xAWR?7(TNsLWm;P8ye$I}i(VNDghB;dP7v)cu|RY4VFlZapHQz1X4t@u8=vRG?yt**M%LK{p&G5Ze^HNV%A;IwkG756O4P%@E2k2+` z->8wBF0eVZoW|5zB;YcDfnE!NJU^cQ#6Z+z+u&I9jW2zCm)tNSP9tClRiMy7U+hgT zvfn$Ppgd0yuCbHnG+F`P(@BfRJ(--DQ8&vx?fn`gNB|D0K-^uZfdYubf_pJOf-+9J z;VClEVH)g|2|@B=eWQBX+sBF3gnyIOB@@F)#E1(FYTpgo0Q>Y287Oq>QId5bBe-~S zgw{HRsJH6E-c1WVx!|HkJ%Ub#Y!HwrOY*l&u93Tlb_!lb%q#>!=g-IiR~#@80+KjF zPHi^LldO$5bbJW1eyoo~|NN;dSFm%0Tr2B7?;Ik^FQ<)Yzuj(emA0SlOb)3UT$~p z2m$h?n)i7+itV@6W{3yq$52Wvkuq|<&Nssm+MNZP9Ie(fStrrT#wa9OPWA-B9g+o% zgE}!n-o1+IfRq@3x1#259&@te7h{z>m-##_LpA_ARMqs&S51WGuGOzfnvP2#5~NDg z9&3dkandLiusi`u3kB}h`IunalNjz$PEuOt`o0GUJ!aupr8dWkdd!tNi7gkq2;|FC zPM=7*Zp?9Bl2WeLb*KnavNXz`)yos%k9=boyk@ybjUH}*wU@C&W!4pw7^_Jgk!vwW zodEkNB7V;K9J#mkVYe_J=qW@b13y5PjXanh-87hx#U9$yapmbq`i#YK(FfsDh|Jkv znd)gBdmUOr8#0Vm_Y+{v-}I+&Ouh9%PFLz+q5zV``H_OkaB0jMSeWyiyT7ov1QuKX-sM6@49Fc!r zLSg{Q>ErWqle3O_eJDy97tQP;al0fss}&uD`!u)Ct})V~-`>msV^JA^-q6+4Pk zO5x?m&wI}s9x-qqME>gt(Uc6PKvVB@FsJ)8%?hXz+dlpxTReqlYNEauHZEECv zL-O}OR%b-^#g|JzPcEVPukZc0Cm|#VzKM^DiTC$!`ZwoP9#0S}-*!`({hz|8|7rsL z_0CT!1UDiP^#0Gt#UCa5Pj40^-uu2c0xXLD%`N@ii$#TjjPNtJwf}v}{yRCyrQe6+ zgkh`(h$`6s$9MPw3lVC?z@_-_9{CUN1Geue;+T)gG|PXE{lB|6go=ESM0i79I3N7| zG5_5;LLHuAY*zrzeg0I)JIw!cA<)GM-$$H17IbPq{&Rfde;Bp@ zfj#|q0tp$DM~I=|xTR9-|Htj&zeDKI+r>rXKN#nK76KwNt&9-E|4t>j|Jze3t_|%B zCP5W++e{PUX5on~xwzOaUFpBJ9oEA#=tCDFj8=4W^ZwK7?sQcpM~+&t2DYv-*il_comte*9P_5l+!_r zrZP41I6eew?U5wRr7%mwQTn$U2^JHKD(MO7jh@}xB7~_{+#J#32ybhDdh4)B-s+7Z zBmbwmgGPLtgZ^S@`JvQP4`b4hp>ChY2?sLyFqgMFHXW_0Q=CfnJ^h(zPeKri^#qxv z>OO4skH-gcWG1dm+w%nf^V_f~dZopwhj$kmTs?$r#j^)xO6hls<%*)jvM3R;&*SAa zf24&(VZYUgplH5=1jLHc;)(qlgnxP%A-4_9)(H}FV|eojZGF&$ zs&y!Zvf8^kl3Fl?(Dvui?lC;^Tzr$ctYutJ*z*p62!2?_n;qYv4T)_H;*uP_e=h3X zf-=1$B;PRek>yTGqV=!*v@No8?WLn9L*_T;yf5Z79Bb;?) zzl)c6A8aGy`I@oQc22LvN7Ilt#YR&o^F&7fYSK03%53BWf|?miXC2r>BOMTnNs6Vop|+tl#9P2;<3;*4;jve*S~NR=d6bAP+FFSok+jHVFxlr-)#hY(^Grcyy7V=6 z+OaD9wl_pPnPC^-B?Bi5UFyw;fb7N|pI$H5xyINvOpQgm&@yrvMEBp#HoW!?x@nQI z=x1v5g2jW3VRVQnAsza|)mYJNnGY!&-{wY2j#IHS+|-*mu@|r`nIcd94dtq_Z`l@H zcA|+Zuh{Mb!y#Da-UiYrYn}E8G+mZlR1NlP_crg$`3DHZw?gTT&5XObJo+gVU`P-s z5m-4^W`M{zo1+5(nd$4@Hjb?L-W<7a6@9F6Az;{H8}CrBU$#i#FcNOxt!mGVjSI;P zpl3~+#CP>F z930x3M{5JL(h))QRhC1Et)Q|5ExQSY4quNOk2YLO_*F2jz;aed(U)(&jTLAash*LZ?jN|o?z$)uWvIZ@Bh6MODSTELq#CQeDWfCr z-->(A$y|D^SZajDTy2Y_zA&nI>!X3;?hj6Fo4+dK2@=STy16`tfS^$o*J8(Dp#-&L zj9i5)K|zQ43O5kichpZ7eLB4q>CyAYqsW#1owgPR{WBZR?Pnbn0Xb0%d7?fX3GKd= z7~4)0yO#;!nQ$fohSvoBNMy&iGD6Oa!I}k{)Vs$m0o}@5^G@Yr9nJzc zw4h?WD&5HJ?ii3O2nfXU^)DQ{h91q6x>;ax;v1Gha?Y?FOdNn~HXe4p&(9VoHI185=VS$Nvfr&F3>`TCh+t-YSv)1^RV=QlSw1tVT< z8|#tuMpy3Ywbd{o9QxGI=El~dC|m{=?oBsx5~&)zQ(>3oq?@yAL(+WM3{)SkK*(|2 z%Hy3Wz9%3fe*HEH(e_SCAwR9Fsu>aE_c_0%n9$BECWcR{m0D|>yb3J15o@6|hEl8& z7AiGKn{3`r*V%mP{g&eDam)}}8~zqM<&n=+5sd0|@F?57=d+}RvHUTjoa)f_xZMqu zcZ_`Cd4cMkIbeLyK(NG5>M!&`#jp~Dn+K31O(zf=e(btb3 zlg6=)5n)#k#SH>9E%J8CFjHL6Lz|6_wS*p14i|_{Na(83y!F2vK zd%V*Yp)FG^qW!zZKE5EAh zu~;kQbKRYC@dF($WKn)<8EQgD><9cBs!lbNMLXgY$fQT05E#<_%dEQ?#^>DQoZQ!^ zRlCXyqpz{mbi<$KpYlT8d*BhPgd27NM%z~@Zy9H{SQMq)q+L$ct#N(HN(se2*-GWB zmr~9YB2P$e95KFC7EMj%hclXWy~aO*!u1-J3$+Wa;T!-8W9W)1v`uyBZF%jF4uiTt zq3y$tpUjoU(Zj>7IO8z9v-DRpKOa=Y2SsXOB9eO`>1~(I8>_}<6S4ooeyOw`2fJS3 zk*5_GRN?PTloou9LccqEvuJmv(y9=#kpUDi3HXJ99+dK;uT*;4WpnsV`r0W_^lG@7 zd-Hgy(qq<>jH&Am^?ZSOzR@G$_8RXB#35Mv?iY_1bC~41N`qH?*P|UsccE%m+lH9e z{C6JSB1k$IO1GC8IA?-sweb^@*noSCEFvS`x|L<0`(SM~`+R{vmvwuKFiR$~mn7H2 zSC%4ZHE>J9I-iP?Y4Ph#JHT#IxvVcSR-CG%^=1O2EFNmiheaah8Co5%{n!V*Qe>IW zjQ>&h>92c%_-JputMYCO|%5F4|ldqJ#r5lm#~-$zr+ zw;ZeOUW7AuBvSgVGZSGO@P{N5#~NU&U=7S-Qe7`gHbfu0HqLFFnUPUTT}dJAodR z7{{}tJZnd!Oi{>zEa@eQ{asd{!BG#YS6}&K6K2>WB6u7y!cJMx_ar~-5thVG71;UL z6Nhfaqmg3=i<_S|fy9S(6ot5TMDlI+y`4eqiIc{G$_qX_gdVIdx_2WX&S9!U*C{+! z{ifNDCC^4SRvbNHS;20Ybq;|>$Nu}%jAI9;p=>(n%%Q}XWp>I72df8nvY9=n>;@Oj zj!jqUevLSj{i?%@6hTup+07!K(bKi1F|@qLHE8ooDgD_9#!XSW*go@pO8KCSb$a71 zM(oW`jW?4&=lgX&F$4BZ1GiSy8SutcCDq_H(GL=CA@B5m{&de$?bZ3XiND`(s(jo zTTA?I(TG_+KaTm43k8f|)$nEVUUP#NA=3jyx=XMVp`}n`N9lZnXT!6;u!QaA8qGkn61Tq!WScw*9M zuN4gyr>88n)XC-*kt!E;mSfe%AQ&cO9Quvz>G$#y#zH5Zy9 zab%R+5%#I0Z@_h0^_1^?kXKW6b?n^&<(1fN-8m9ZR+}Hv%5eXTg!EK)mlapBE6SA# z7n3teQXJm=WLL|M?%CpB$5~r3}H6>8% z5vl|ke{U&zubOB@Bg(?5Dpaj(V~RWWj^Luj`ySh-&bJ?~2uCH(NF3L>Tf?jC=ew_? z7U>P%SicWc6WGHXSA)^fw%!en06c$`<9$3|5L@7po+ua%0-D*HM+UdZ`SKlv?l!Fe z$&^BHvwP)QFO4f1l5V8xy-lbUha*}pyR^C0u9MrP%3w+ofn6hE1CT$aoH(mRO<$@~ z3BK9Ckyx!wEt{O)d#SZ$plcksq^Ci*BK#B!Z+nsJsr}~{Q+ml8pRrrax1j2>#|X|9 zHP}1E0p0{Gf9u?s=s0kI7Z-*_)v?O%v-%Nwa%l;R-GAuD255> z<`g`S$p~M6T~?iq6`$80 zNJqPWLACULCl)9hORlxH6-d4~WEYNIr?QXFMqaw1#fd*qYHROd=kwal4|i^BzvWn) zN1^RDy{wA_Z_X`mzR}f}lk1>?E3`(VoqPz_l8%Vka5xEXpZmvnJ2`O`HLMBt0`8RO zONEpvVC>T_s&CpwhLIt#-TwlCG9nlsUXV2w3@&yR>oN6m9l-cq>ohV+`?J_I8gdM0 zzonECH2yp~Szh#b6!|rOYa$ygcI}Oq7JNR4ts1#@aheD4KmEhOg@%u`CO3#?S8Eld`nYXbRqBf^oeIQzXk1ERI`(DHwp%4Pb z(yB+3EiB2bpC_O7R9=sHZ)NiB?54M0hfN;{R5wg`^N&wMy}c&3`rXU$A?LJX;O-(N zrj)cMaupI|Hnr}ewPNACtcqKj`nC&u||4EK*v9_mX2(U{%mtWGP&G zG9MI`)q_bMQhSw3+hR36dY;az3Y>o^Nh*$9!meI>S@iIX8gHmlm^&(39Q_}db_r*_~y$v{8 zw6r&np_3&(jl?w_)^MOk<+(1Sk$umDb9A%|=FfXllH+Wc(-G33iH`B(XHR3=gJ|u` zE?ta=%&q>AVg3sgH9}bViYM#bF#7se8`KY-$DoMysJ^HwPkNQWh1{2&JtSj9?I&@X z7D@ZlC{k0jrqLwsdAy=gZhH1qn9VXlJXW=KAxtq2iWEP80lN0c6OK~M&TriNo8fN_ z1rs+&$n;wCuGOpFM8dLn9onUFhtGE3)}#^1zX=H&e23ydhrL(x{#8sS^H(w#LOgh40DVK?gd$QB7I6e6iO)u_&m$8n>~ss z_c9m_L02$}hBxW3U8bC1%g@5%#^Ih7=j_erDRAVD{}_FT?Av~o05f(I+E77kO)LDu}y zj`T})Xi2T*exVB+zjktp8+&i-YkSdCHy`%-kpV8E$LN4cH4kbc`w0nRvz{7O8PxpF zZICFZ%=6f`=7(!${3xdjSR$g|Y6GU1Mfv?o{bpM!kEe4StM>6QE+(nJ@|aS=c4x;{ z!%DA1gX>S3Ko*O{2LY$LWiDA6?llK-`s+I9TNj3duUYJFj(yJKNVZf%tE0aCKfbs; zZD>1swGQ(wUTGkIQ@innmnI)A?cdH${R}>@fqO@uroFWr+&DfS*j%b_|jBH+1 z)HkViP7M7xT2I%^xC*y;DQMn#M8ob@`C88_I@SKmpg56B5bA1rrTy;Wj8o&Flzvpx zppP`;NDJ2j(1fEs-44G|GLp$+^CxK1;R3?Tb>Gh` z#VZ~2Bh8lY4_4Dxn^7U$tB(#^%(c;tfXWH*(3QjLdN-RKW_f+CbNZNIz$NRs0pN4P z(Xcl4pS^R8*h~M|y*i3&o4Eyt>c+A@s|JQ1W=^}Gf5Cwd#O`rsRm``FmMa`$PvoS~}G z!e-U{9J-@V>6Fr|oFN=3_#K-0PQ92~+lw5A71pd>$aI(=BsK$yK}n@|Uh{O$!Z(~n zSVHtR&QGdw_x4HJlhT4g3d zuo5viW=D?dut(!Fb$Qllu!)-3jl8R_?Iv$YF0dNH%CYiE91(S}j!f`wjAr^W(z;!z zgs9skn7F7U2-T_Ku7|QBTOR$g#Za%MpyhIu6kK9*uzu~|i4AL;a=JNU9eAC^_sR5= z*S7)(_&7FU=g=tIwB?tsBYY5yBLaU4i|3sz-UR1(KUG?db9yQOU|GA_wzbhl@-|Bv z*mE2$vP()JRj^I1)hWIFhS{1^JyK}p4a^{RLq&QPN|Qb8*eFUP4;5JO|IKk-Q)LM`p>)9d>e>rA<+%mU%-ikI{Zx$+d!2j7{mDkj# z!W`Ye1DxFcjw6cZivtJFC%%v~Z;6vrh(qUvu(%q>YKfOqoc24g*4w-%`s{bv zDOZz;5*+fBd= zxw0rNj4STFr~5sFX-mb+p>E*1wPvTQeq*lh=QrfRw@;de`UHuJN<$KV-8;JKHvk(z z?Tw%2v{m#}zWg)n4xsRSQbs@5+9uDv9GuQE5xIPbLcVd?`7;=^mg~vP45a6A1xKZj z?t`0H_qqorSOSYYYz`V^2r&0E>h_PC8qdoMY~xos@lf~?=s}=6i;~0ju@}y>B1-zh zG)B{R1kS}M^(Dwjjh~WRL$TT0!?YcgIkWVJEu4Zmn$lUHl&q3m&Lp>`4B2nrd>R-O z({D00!a5B0oKmLJ5>a-f5c&w9AJ@pDd*9B$%FVr-TSa*A7q!;vu7+XnM4z21CyTv` z_H@1K#}0?`;an8)BSu~CEx=GDOy>y@iUm9O^UKy z4sl@!(d1(OSW|VnpoPD2=G@^0nKfru!;|VwpF8tB7X6i;j{R>F%EK7V;+CD7$~6A| zMR%BJ>?k{eM^2s=t18bVMLNLdMop6*Xta}b-cPO}xO4oztJDm+gCq7G`;Ex*q`S?? z?g)wJNBseoh@5m^{WkS1#t-j4OLd=T|~| z3e043=7xzk)awD8*lw)jm*~6Vi_mtViNWKO_~Kz?3~y3|VZG%F%YoNKwH-z< zW5RK1`|uXwUEw2d59e2iiA1f6+QvRTkSDPDdll;!e@hkc9t26mGv3^MjxwgWNzif? z9>+Qm46891b1+&0U(zmkLKOF>2wnP|Absj}Yo2K$-U6=-aU`+VYsxp+yF3Feea0s# zO6ku=jGrT4(p_O*V6! z>Ht)$J2}@N!>GpJ6?TjT4g32f{HK{qz6IVN_skwZHh7pQXtSE|8^tHYswll`Z*=Zo zr-S@H4a~8Jvv)Sov?OYVe?nKi`+;hhvaM-0KCj#AwzGJPKU4hLajy!>82)fYb6IGT znIZJEE^u^B$jW-4b04M?y$R#i+Rk~pc6OqeX|!bgA-V5R=s``xHCeRq>&O0zFL@z8 zYd>%Axdv8W)Mzeu@WepdFb?mma7?ycoIMxperz}0wezazAholxtQRw#d_KYAicELA zctbWUVq*>JEX~KQSbpf4`A|pNg0{<86V)~G-#os!V$cZRL^YJ$A$#3&BK z8v$#im-{yfGf7PAzv2$@3FY3lK;N<@m&;-ky;7UH5bW4@54J>>B90~=!355O9x;&6 zL8F#v40T1W=BO-zo&Nmh2*~0_aHOEGw9EIJpLD?w!#KmG(=FgH=n5GgMIEQ^3x8b8 zjb}-pbx{&!6!yfdutbvO7+&zLyq_?}20$YJP#SMSwi)fH3Eyv7ocV!f17BzLkh^wh z_58A=_7hk(eCCW%^JjT>v}htSw?)e zA|&1b37!G+xZ%O~da&k;>x{8C7dK+!m1k4bXUpwg3mm4W5sef2rPdi;g6PwMeU9{% zPYn)_0HE8kOr`L9%Y%z17na{$*TJz76}x^8C##mhXruHpZ}(*rRME%EDttS4%#Z4# zbR^}HI!&R?&~^e8X<8BLGkUw|nVia~QB$E-=m_>iR#Yhe@Vva`xXe9`@$H%ka4N@k zadGo4=;pyE-noRhry~U~0#SAchtYM>J^bU|Nfz-%I}xO@PKi?H*UCBBxknT{7lxN% z%aW(7PC2MTFx`_PU1p71m*ez4$VF*DbJ?fdyOLj}-bJBpn{)%ZL4(;ZC4Aej)HYhR z8CYgJ*PXx-a3b*N|IE=7*se0A?A#BMwWmd!WN+U`QB^cAc3iZA-|F3HSi%V*XUe-_ z6LHxFkQ}A&HwT;GYe|*+(5-duth8c^R&Eo2R%R21oW`u33u9cOqj?cX{AR&Ob)kJg zX2uXGCV;;rayk+B)^PeOf(i@3R0}NcNPbT`{Q38UkLPl5T5Q#Kxw&8U7uommxH37U zNr~748DbfK!N+%BZL>~`1UrsD)f$D7<)DXh2tFGuG=k+CXP*$wg5N`+Ep65-=|eF; zBb2!52guMiTLcTS0yYV5QLDI-KzTz)*(;fFXE*o^vhJQ!SQ?m1?%}vM z$%%S;!&QNKK*sY#*Hy5y z2R$BRd!1=L25AuUz;2}lm%0_Ic=u`U1@ARsiT+OW!8~xN&hA$g|1qovlNUk}ZsI}8 z5OyJ4^NELyTT<|bKj&&8T^;P0UIFA?!8Tt7TFp~YSKU@XJ3aoTouYMB_|CVgt)3l} zUAt^8S8Y)zAtdmfo_@H>mS?n!hwa?u`uBYX{L zZ^RDHp-(gatLumnLqN!+$H-;I$j#}Zyi3tt>V)C)@w?|j0@o3yr6NV*qC z)pb3{xZUJQ%9tUR`)s^j{p!c6;T)xqMOY8rCsT4ph}cp`oMXrMlRQ*{zWvlhN3LJq zoiGd*qsE2)TmqEgv@q=N(tl9J0d&8o=Pm2sW=?I#q7gLVJROdjK67><5;ZrV8+7(4 z9nKs_8AWb4Ae3)Yv$p}WUT*nV0|>e%tFh#H-ozOvNw3arpNj;2l`vSQ zQKL)MLwTW_YVnDYOAC~xCEy-3=P|{`GnN!9n$CTD_mYwj?vvie zc!%o)UICDz^`KZ`aKot22Xm&qi}`~_PTMs+C*wB9a}utqz)MC~WOYmXkWX)bhX>` z<_mPwoak7YNd7PFV`qPN1{iRX0&6$gTu!%NvRdEAUV6Rq7m0qvLRu0cO6lPN8dhx{ z&iz@{krKqxe4(A2>)ogeCUscTmqEJAzc8L7ZN(Ert&Y2F7WtMG+Q&xKS`vR{^ZzwW zDR+3ncDLFYn5YZ4APX&j{}}`pqNGZOQi3Qj4TG5z7V&P)PXz52cs~U#$<`W zaynvkHb|%TB8df$kBeDNFP(6}>8-84H4k`*J%$RPx6RSSJZlrSg0Mc<4|j*gMhVMB zk;0Y;M^i|Q;~;NxQ(9T~j6WW5g6h8l+EkI-|Aq0FC;yS@^GD(jjTk(v2=d?HX8(>- z81v+jLWsU##`EPb)x$p#{3wr+OZ3j84@B+RKfvZIWqfHoB^;csw(ROvC_!G`!?M!F zDfaoY<$1J1r44eded7v&Ec=Q*+<3mpneApsZ*Ji;Z-4zbw3ttiT*psG+AsftFFY~; z?~vsUy|U?GV7Ok$yndzj?I!k_h~smgb zd)zCFVN76uuEiw-6x7`2xU%w!__oW8-*KY=dK0wtv_v9P@ASQ#-hS8v3Ey+GUoD%W z*L(Ao3AvuWi=TbX_s$G)s_0{H!Jy3MmQVCMu_ExBi#m;4OR&6bx!uO1njtB-PgMrH6f=e_|pP>1kBG5tj_rXe!Ce))UK0S!&zSM24Yy40tb;7T4Z;k7aOJ8j5T{qg`?=3% z+E>Lt?Imh{!59-x%Y5?+Os8RZ1Ta;q_A+X#oMvoi>wTf_=RP|i9?l#?CE}x9DV1y; z_1*Eq_2=he4hD$s0YKQ@gWZ2r_Zd&qS|bTTh00%o&`694-^BC}Cp+LIwI8q3J@*9( zJxyh*4n&=0Ya^!dM)G0h@=XD*ceRL9p=IM>pLacoTe^QT1T33R7aBE+lz}6AMoeZ1 z*z{jyTu@NcfJj*Or=d*h#cFZ#Z{KpB6hjbf71$aZl6r1;6PGu}XnkdRh6V?IIpCU) zG@WyrOul>M!0633_`Aigf&>?vE`dGQTr%aLz|DC`??Afu?ZydX{B!-LkB%pk48)ut zKcU@xXt=u?k{|fO)tq3cr+fjF8cS^ZQO`Z@56i|6ih4P1@65Ezt3SMjuF|x9IU^pQ zKOco$#)PWo$)yVs0z@Bg8?6?z7KjjjX;+1;CdM`SYs>fQ9P{s&btFPur0=42S{eEOU42~ zG~&-sQ11uB)uy+`5@#%uT)i&g8-YagtHCYngwKt70*hgxcf!{P0fboVcX9@Y`RK$v zDxh1RfYcwpG-^M5@^F=-ec*$2e0Tj=iY2f%?B%5Nn-N`*Nv(JA zQJTb&$7kk%eM~gWqs0#I*;>zNJ)OK7=-#u#?eCc3zJe?KV!rre8Y3P`vu)9c6JMga0Jo%5;j3T(m8ufK#y8i|0d}zF>}3G`ZkbIrL|?lmc-S zu5y@C`bBk3^{YqIwfIhh>Mr{e#_iRr#by z^a9&G5_l>5>JYUqJf?KN+LIzE#D76Q8+Qu<=?j3CI+hgZ zL$F!~>ydx@iVqxmf}2LnV9msD^C{|o#pd5Xp8{@V^!9Lxy>YfKqMAY$G<+E6)JTto zv>^H>rMgC?vCNT$mUQ#NpX7WW>@rwvmhHN6J|KE#6!*y-v%T>*vdevXcYW!=A?fDP zS^@%aq-o~C@m^wkrv0(u&_-=yMy%_S$D2e79cEt^U9fg;`@^7iBswiS+>$=gOJkS& zVEyHU&LEV2(zGS6pa-eB?e-6jXVqBL-uYSb24&uSpV}S_7;e=Vpks6AAX9GU0RO^f zjE)TwYKh|o0N}}$xXm{Q;JPFZ+R-li{cS_b0l6_zCc6g+B8zDdV4up~lW^Ck+EA3< ztQ|Q-5@%{d`V~wvfs!TsrLR&HO*1#2f$dJ3G(S`=h#UZF%VsQCUtUJUwiEC9Q)0zb zA**}7*GGGDo$8_yl;o4dH!E$D{H4>$cb1A7QD0muK$O%Ul)I+F9k&y}5a+Mo-=OS{ zW5+V5&2Zt-tnUOaZ;$c@`2r+V^NUo(j_+4vhd^8uRrpp5tS2IY07V2pf?s0Hj{yWh z1yTIp#9?dVFkE#}_TH!(Y0d=y062SjF~!rIoX2tURyfkw!Bg`6G(NRqM@fM=SUl?jt6#4jeP6>+U5m+oXlnZ z^&<&GtZiDdvm(id4l?-t$D%eoOyAUpdyiVPEXE^j_6dH^x)L5P)IQ~qL*+okVG`@x z$!ryEehcL>Bz6=rpV1Fz{HG7f>ya8?l`Omc-<;-YeoKVk@b_C^XmlLp-m_WxRk{AY z{YR%or~YgKh*8em2FDczO>a^wm4XW+pCa|cgk1)`AIun?%`%0PfU>*Z1cPdCrrjoy&-F=_61YQ zUW9gkrt{Ae!s|uv?NYcm&-Ms`T3yS=MWiFwy2#V#&tdBy?ME!H<#-VFE1Mnp={T%J zUStaNv_!Vy0c8UojqInL-zD4`(@MwFYOrzY&KW6SEI(a4m_0wW0rvr?*fuD(re8%6 z%@k3o2c2x1O!6k_Nqhp>Clx%Mw{qNv(X7)CB%MO>ZR|!Y0C;r{KfmBp-LzzAHY0xD z;R<`teK!9iF(3jRja@S6JNXbrJClYbw?>dF(b@5mTA=(H?oZ#(9~Nue<%}H1)|I_w z`pwUDjp@UZq4X3_p88X18Dcq{>^Z@dYahA%IQh0Uo!{eLZizgzxO()31~`B~r%Z#1 zvBxCxi1(+3gM+t7z1xq_t{#!|$0ZExS8Vrd#AVmrOCnKzjiXc4 zJoVPl*V1dKcxYL0Y6TXJV+W(YRe8ZQPZTd1d3I76W(oUAp{H)#&+pgjd$23-VQm|%f89*)K6{q;|VZrSfR)HSypZ7y}%3yjS)k6v2so>#)?L9UdqsF zL~n1GGGCHwV1?De(q{im9%Piqf>y|mJM}b3&4A^t9gAW=J|!=uCU$2$BnOkWK5ZY& z(tTG+cJ8=mJsYe9OTHP6(X-;D>~-fNNl}}Hb1`SNFpmz-x6l0 z+X|wPIgdo?joOx`q(W{mkJfBMs6tBjBPft_qyNegdz-hf+gps{=K2#VQ33i|yo`+> zKOppMRVN4Xasy)n+YEbQ#qj*-eq2s}$1qDW?0T{QSF+H4n}W5$vY`3aumH&{8dn}; zxA6)=s;uj%^J!1mO@d_yhfwX^Bd>r-OokgKTmgA1@zYE^-1QiXGmJ#JvZH{OI|qpS zy^-HGe8)a9$}rLsJ0MGm$=Z901nX_)w$LO4ZCybide&Zmzl=j9UDNV{GuxO>xc)H} z#gYb?X#TI=m*9Lc4%7Aa}MDejriOu>D)ns2Iz%Lk|wJlJG^$NP+Ka zeEr=(mJu}5)2=y+`yIy*vJ^jo&hSoe*G2LaGvjp=_N&4^^0?rsTexX@NS^ZnP!=V1 z>ydBB9eS81?vu+|oY+Pq*_$dXTtaz|qBn{$UOQ5~!(5-Rr%uJbF_fX1=+L13Uag$O z4M1vRERfw=k#gnYUrJCnXfG94Ay~hlam%elhV=~?AY`6}r6p;JX;($*G>F&NQhBZy zQga7{{QDNo@GbCYL_+fEcDFh0u^m48;mwDq&{L_xaxT@?N`$KUL;W5KDgwbHvo2i5#PZT)CUwv=0j(Sx^x1n9-lU z46@g>VAoSwD!%S=&m)mfJvR%j%%;dg}3ucW+KOP3{)3=ZUvnrF4j^#2R0$? zO`j_XQXE8U5_haAB?*n3^kg0Yh2N1Lku{rn(Bi^yXAh%Amx)CjE)dYUR>ppOVxxy= zgwJ#}*QBXUX|!@VWY;S&>uKv$CT!0dVb@HwNm(IpP~X zkr=Sq2u`kvTJZOuex=XV>__5Eb$3;*RQAjNp-_L_QFqDc_^UZ%aFJmCdJRk2GFn_M zy$1tg1J?2p75qo8reT&nugr1|C8m&4veC?lYiqi22GNbsdIC^nO(bXQ0@} z;2yvHBiQ;2XhJ&B{7G*_3cWhxZ40S>UV7Y*?RFfsjsV><#AdKaun?WXy^LbfY7s;@ z&4Tp?yRSGJC9n)y?@D2>p>7G+E=nW-_g$;q?=3bS)|(G+RTGG?6e5}~2<8KR5=C-w zSkK0BPnXCn)RxM;?7?B0CXzS#UYS+EKoEf*LFv-&2m9nAeQ1=gJ&w0)*wb$79s=PV z_J$-ljY3;4N7ff1>9K3hO~{7JoF0??4d->u*6TNkO2RY-9@p`AT<>m}6Z6QWH}4-F zkoE;z-i+CBd6)@t+IWc2Dv~Iy6lLfq(tVwFtx^|7ovv3Jaf)pH{Qg3wZQGp2@%}I< zan0}kVl>uVwe!O_5S6guscRniL7{XRxK z4+a(v0r~M4v2cXC;jOm{64^@`=-C-t(MyaerY~txa>5UH%O<{skIN-6M`pL#OF#6O zeG|N0fxnEj^S$i68~Sl(el|d-I64HKq;nYnhxopHk#RI7ZKz?}tkC6JnNMX+%9su6 z%`vW30?wVkeurq)?RQ3Nu5+>MTHbIlSiELAvlO!!$@83N`NKp!hLY>R(#QS7>$^fG z8~T-3-{xJ)$H3(I(7xGOubH%LSDs0z44pjVN`J_h+39*-v#TqGsBJBkPv3`Xbv=(B zrL~*%dP$Ndo=D{wkdfk0{{bAdXI2`+)AyolRd*syH?7Ks&fHKse-$+0RYeE&6_zx= z&1ajVGv7)y@OmXb{Mkj}v?DzRpzJc=R(YIerSxuJB*kPj|L|3DlqSjJ^X5;(!C^+2 zOA%9r1iln*j}rt(1!DfHud`AhQ*Aa)i@#YL+AwX4p;I7pT?=E-d7(%|m|eXq@l6MX zr7f!=Z8T%f`8Zc}aH~C?Y?<$Th=U_s)bM&PxpG>kwVJK7F{!It95p$frD*i3G@e;|0a!g16&^>7v*+Vx}$_&VNr=D zi!i8SG`fKAv>I=|sTTI%TApcwuw^MhgSqV0W1odTu|v)Ca>PC3vyy^Scv%y03$J*o zAd>Uq;cll=ld8=WZ6x`RYRl+@=Gl+h&AYjkoZ~;2`SwNEm!kxPM4FCcn7>e^jIKc0 z@@7)`y@M*~Q|2hWJDT^tN=i#l;696`<*WH9vkj%JJh8gPXP2x{!H-A?(P@&Xn64M zZoAR{cKX2s2IAE#93oaSbH7AeQ`C=*|v2ud#p`#7)bEN<`XDrLkcm>(9} zy)Wz)$>vd)lttc2P#CIuuczcd@6DsD+SX(vqe{|pvu9l!if83fstWSaxP#CVPLYmH zR4Pohfv-tD(We&6Yo}G+i<@ev8|Nq7zu`jXRGTN`#jH_?US)D~r-lFb{QYaWdP)7` zS5Im$YuHTUvzV|#h`2XR524qp@FKU4dhJb6{#lTzx4A+3Vn+tJ?q+K@pGU(hc7FVt z)al(0!frz^2u=UIY*7@tx!VBl)m>i~gf!?liZRlRET;JE>3&sM%+0#r15f<&Fg>?D z;G0K|ixrbwLJA_EQ1jYN*MI$fK;_+aWY1yKaVF?_7H|lvVA;d9^OroTUk?%Oivy& ztU8~^&EQK-UAk9pc`3xdH4Ly2sLKKs@Y~7xxbDxfl(p;;UaPU}pFt=lGQ1cm;)_6L z<=;+u)uG?+$05HNi*@Pww3~OWhJI==jQs#p{-IO@wsWWW+AV+vOp5{xj=w{!jCL=s2 zoyqSS6Y6U-`PABN?t;K@kB4{hP=KkKiyNI=-w^KAqIW6oI~&;J^4d3P<=H4XU*4tr z+%#$(J=|VfcD)=)%=JcnN%v3-NhlKbPe7f_z*s&x<^TG}iN$)C4|jiaVfA)oLT~ZV zO53iz>5C6D0&-lNm}fYf^P7pqvaVqC(%U%aN2f*4<^K|Aq|Q__QM*(v80g7{sp6!; z%|&X>O4EcLBoRw*3EM{7=*U|5S`kaRqvRRBtgPd^d~U zaIzZnZ*Tm>DiI~omP>xUDSPX_woG!&p9JGC0JmmHhE@y&iE)6z`QlIUlgt|fYW*>( zY!d7D>KYg9$M$hBn_@Bm#(xbp#J7i6lNuIolZx^oUP`n8@i&E~f8CA;A4EU> zNXRu#fSw<=z^iBcQ%gK*KrOLoZ~v;VtD3O2AQ?cq&`@8R8> zk{bjaddi1TUly-jz6c;S0Ny=h-lppf6_N)e_+97teD_V z{s;YHR05XA&GSZrf6y8+M8GE$v@6b-{evzP2mzWMKeMeP{|_^aPY>uToS=T}?{l&r z*a+mQ4=Q&~$Kz2y@N6i56v2fPu!QkD8AO{8CDiV@r({Ge(v)>jI;p3pA_uF3G$tSWLrj;%_0tNR@y?B99Ggz z*Ze|Fb!x9I4F$N%M4&%@8Hr`YkERT^?a*Dmi3aOutLBG{d`1h=@3ke70ympJxaFn^ zQ~vNr0HAQJ?Q~b;yIm#UP~aSPse0SRwF(K$sQb(L`~i*86kax4khyFG2{)(9FQ4YA zi?)YE-d-hN7X`0vUCW+}`RI}Qv2W+^#V zvdgGzhP$_7=!~jjcg4d#GwlBuk?w61kyeB z#vFF2ka{YNCD!ZaArTpR=M!Gu`#*KLb^6XWPa=62zeNiu8wQAFBh5E_^}DLa2}VK) zqo#bo4{96`?{0e$dLWiLh1lJuLFX(Ehff7eGIf(S&F?2!S->A#f;WP!{`#HJef`WS zf`5-((fZv%z&-qgVMOFKgwRmn4wu7aa-}5zQ~20X&-pa_xO14-bhy7`_RT|RrA9gU zy3Ug#6 ztGjygT}Vg_`A-ZZWMhV|@}D83d)rE$9n8{Th`fUq01L`Uuq{Db`c2BtoOusWZ6#!j z8Y;hfEARQi1GL=cwHNEq-+|P<@3h^^zip7jyXtzmNZ|{;J-C|Y9ed{zOMbN;cufx< zOyOd`;Cv~3e6#Olnos5$vi^dCG)+)*i#c)4u-Z@(gYpM8pxEZ0u=>+w7SAMx|4|HH+oZoQm8x=Z$1i`WN zhL*Y6J$%=ysuA!J*{tL{4pHik8;TlXeeV*LvBb&3=Cx%F>E=&V7-8Fho&-zvW~6(w zETz>~tc0CI=9xZQsG{fmL>MoCGvwc%CK5cMap%Xa`uRkW-&K}buZB<5uxR-XF??+< z9E1h+S+Lb^yzDKRh4kK{LX+t*?#Ajb!uNfTg52oj)ZFgMt5M1V$)48Kwrk9u!Gsfd zV;MqwlO4u{RZXof(gk+f?@hZySWdtDx*gLxKDfrVusmwPV>2nqTQG)t*}Ov6cHy|Xsnru$t?2&{xc=flDD4 zozG8_9%eMFu@QJiB{7NOuf*ldapO*3o_4-A0bP^fF4Rz zo{kFk*k+I?&g>rd<@W_*3a8Sd$wzcy2-AbxpwS!_!_y9vUxLfurqM+#vqXvPg=jU{ z8$WaD{&~Dp(|7U(WqK#)aL4GloXk0)$b|^dSD_}AJEmwKGx(1vwa-x0{DlF}sFt*G^=Cx@94bk0=8u|weAV@ib8%zW#>)g&=7h!blYFNKW}G}LtMM{BPn zu<~2i>ryGp`k&7jvW*I@*SOhS9JsB8=ARfpY!?ON8#h`B&Lg1t$TZ zc-c;X;_V>S7BB-6tGdy(ep-_Ngh|t1Ka&5^h2cvRu88v5sy(>PWmrfy9r;eSfs-Oh ze$aSa9+2}|&>)|Ir%GL*H7A|J^=qG-(<6qUr+#;E_pO+t1!S~Ffw0~tU|rPrmxe87 zRw68~4sjJqav0zYe9jMv(rICG-XbiyZFf24N-Vo=-xD<$EblaHMDI-JGA^aHv5(gv zSjnhTd<0K2DA`|~jdoGrx4hgh6?&TqcvFY|?cS~1>tG;iDJm{rpJu4vCfsmc+%&EH znfa~L z09M=Sa^REFUm8ve`{iRJnyP6_9{Z(4>?>W*Q@(i%oY(=5{<5qtKHKj~Q>2ypAzsO_ zK*k(7#5exun2t;)3xYs>k4#$ZA48u>WO)Jm?W^m!zBLL|LMfLt;oc~cXERIu!pki? zfwaev)=lb(Boo#PtO76FT3^1ilNu=*_1vDH!BfrvzSDm_iRGp#2QX;izpx9~w!tMRwoR zJmIT>$Y{wX!JF0(2%lQpuCm>>dK!;a5{!;Y!+qoF&^`#5iA5}U8C8G9JBhsw79)MC zrmo&YQS}9u^=|2MJwewld{;?`ct+n>!eFidTh{_oN?zG>wCb?z?HeLRo<ga&x11T7TVl2KY#p0vV%`4-mK)~W$(W8nrVo93PMLZ1d) zDUklue*W%+RUi*bgRExJX3hP39~Ab2-6Q-}<)#H`H+Q`#c`U{p|EPemc&f)zT#_Rs z;}rD@Dqy@%#9825Qa`HWa4M|S^C_D(+(7$#`;~ru$hM(7SNV2pqo`bqTx zwZ-UwT-+v_Q_ zJrC7v$={T)kJ4fE?yj!puIqciQicTKgqnS?G8TF21_za%!lzZC1$#$9 z9e7a0>%E{(Z8weT;TK6_0|<(@a|{k(cf#Iy(I&=ni9}X?lZ@8Ou;X>tA&Ly2o0;_k zNIJ!Ks;6nlADB=H)5Ay?fdZxB>IYxgC+{YxF!|W)NAj9q<0FeXI z>ds-m($1Wf_rCTNxW9znvrJW_sQo=@HQl)88wTRMtj#pnrI96gIhql7UDw9#^upsV zDCp(GddUP3{?tcfidEG~EZN#!p9FJ1${f!}zlkbbY3$Tj|A5`emrtlF`BryMJE=5y4Sc%A@++$toOsSK>)%7x>HK*Ba^FhHg&cDMiQzStQ- zM;A!6tiuH;{xAw4htGsV6gdvnH{CCll7c>1YZ~nXNt?ec;20PiFKg4p%mJ(+ei5p( z?DLq7FN0K_{@@fSfil)C^4Z=L(|B|cFv;;RC?C{yqOI9r;kW@MiTt#TFqbH{A35|3 zG*?>p4{4)V=64|NRa0}d{wDxVtqAZ?vWM=6FMjXF|NL%#F#chWpyS)c(tpATl?xv; z*4pjbl>gDxfBix)=U-G*q}BA%N_)CgD_-_c34;Kre8`F2g$q3ZYvy^=j1HWP*c~pk z1s54lHB*iQRh8f`0})$?BQ2@79rj*7EGR3p_3FcD;r5nEe+D3}Y6(CVvqqhDrKw%+ zSxOs&03_aHg8pJ0fV}Zkb+!L2GyfL|0DeICZ^ey_Cjp>9J<1wS{xXU`zgi_AL>yPY zwclq)e3IIUQfu%Eh zR7D*QK}S9F$JI0~I_z%7P_NO=`A9zbwcVC`@YYIbAM_NhA#L OBlAY_b(y$P!2bg8d(->? literal 0 HcmV?d00001 diff --git a/docs/.gitbook/assets/screen-shot-2020-11-03-at-9.35.40-pm (1).png b/docs/.gitbook/assets/screen-shot-2020-11-03-at-9.35.40-pm (1).png new file mode 100644 index 0000000000000000000000000000000000000000..996f72267ba23b38940dcc98c8d1ce4df7b691f4 GIT binary patch literal 157176 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%0D5m4nZUg%x|P=+9BqN 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 literal 0 HcmV?d00001 diff --git a/docs/integrations/sources/marketo.md b/docs/integrations/sources/marketo.md new file mode 100644 index 0000000000000..5e6d42b5f2534 --- /dev/null +++ b/docs/integrations/sources/marketo.md @@ -0,0 +1,88 @@ +# Marketo + +## Sync overview + +The Marketo source connector syncs data from your Marketo instance. + +This connector is based on the [Singer Marketo Tap](https://github.com/singer-io/tap-marketo). + +### Output schema + +This connector can be used to sync the following tables from Marketo: + +* **activities\_X** where X is an activity type contains information about lead activities of the type X. For example, activities\_send\_email contains information about lead activities related to the activity type `send_email`. See the [Marketo docs](https://developers.marketo.com/rest-api/endpoint-reference/lead-database-endpoint-reference/#!/Activities/getLeadActivitiesUsingGET) for a detailed explanation of what each column means. +* **activity\_types.** Contains metadata about activity types. See the [Marketo docs](https://developers.marketo.com/rest-api/endpoint-reference/lead-database-endpoint-reference/#!/Activities/getAllActivityTypesUsingGET) for a detailed explanation of columns. +* **campaigns.** Contains info about your Marketo campaigns. [Marketo docs](https://developers.marketo.com/rest-api/endpoint-reference/lead-database-endpoint-reference/#!/Campaigns/getCampaignsUsingGET). +* **leads.** Contains info about your Marketo leads. [Marketo docs](https://developers.marketo.com/rest-api/endpoint-reference/lead-database-endpoint-reference/#!/Leads/getLeadByIdUsingGET). +* **lists.** Contains info about your Marketo static lists. [Marketo docs](https://developers.marketo.com/rest-api/endpoint-reference/lead-database-endpoint-reference/#!/Static_Lists/getListByIdUsingGET). +* **programs.** Contins info about your Marketo programs. [Marketo docs](https://developers.marketo.com/rest-api/endpoint-reference/asset-endpoint-reference/#!/Programs/browseProgramsUsingGET). + +### Data type mapping + +| Integration Type | Airbyte Type | Notes | +| :--- | :--- | :--- | +| `array` | `array` | primitive arrays are converted into arrays of the types described in this table | +| `int`, `long` | `number` | | +| `object` | `object` | | +| `string` | `string` | \`\` | + +### Features + +Feature + +| Supported?\(Yes/No\) | Notes | | +| :--- | :--- | :--- | +| Full Refresh Sync | Yes | | +| Incremental Sync | No | | + +### Performance considerations + +By default, Marketo caps all accounts to 50,000 API calls per day. + +By default, this connector caps itself to 40,000 API calls per day. But you can also customize the maximum number of API calls this source connector makes per day to Marketo \(which may be helpful if you have for example other applications which are also hitting the Marketo API\). If this source connector reaches the maximum number you configured, it will not replicate any data until the next day. + +If the 50,000 limit is too stringent, contact Marketo support for a quota increase. + +## Getting started + +### Requirements + +* \(Optional\) Whitelist Airbyte's IP address if needed +* An API-only Marketo User Role +* An Airbyte Marketo API-only user +* A Marketo API Custom Service +* Marketo Client ID & Client Secret +* Marketo REST API Base URLs + +### Setup guide + +**Step 1: \(Optional\) whitelist Airbyte's IP address** + +If you don't have IP Restriction enabled in Marketo, skip this step. + +If you have IP Restriction enabled in Marketo, you'll need to whitelist the IP address of the machine running your Airbyte instance. To obtain your IP address, run `curl ifconfig.io` from the node running Airbyte. You might need to enlist an engineer to help with this. Copy the IP address returned and keep it on hand. + +Once you have the IP address, whitelist it by following the Marketo documentation for [allowlisting IP addresses](https://docs.marketo.com/display/public/DOCS/Create+an+Allowlist+for+IP-Based+API+Access) for API based access. + +#### Step 2: Create an API-only Marketo User Role + +Follow the [Marketo documentation for creating an API-only Marketo User Role](https://docs.marketo.com/display/public/DOCS/Create+an+API+Only+User+Role). + +#### Step 3: Create an Airbyte Marketo API-only user + +Follow the [Marketo documentation to create an API only user](https://docs.marketo.com/display/public/DOCS/Create+an+API+Only+User) + +**Step 4: Create a Marketo API custom service** + +Follow the [Marketo documentation for creating a custom service for use with a REST API](https://docs.marketo.com/display/public/DOCS/Create+a+Custom+Service+for+Use+with+ReST+API). + +Make sure to follow the "**Credentials for API Access"** section in the Marketo docs to generate a **Client ID** and **Client Secret.** Once generated, copy those credentials and keep them handy for use in the Airbyte UI later. + +#### Step 5: Obtain your Endpoint and Identity URLs provided by Marketo + +Follow the [Marketo documentation for obtaining your base URL](https://developers.marketo.com/rest-api/base-url/). Specifically, copy your **Endpoint** and **Identity URLs** and keep them handy for use in the Airbyte UI. + +We're almost there! Armed with your Endpoint & Identity URLs and your Client ID and Secret, head over to the Airbyte UI to setup Marketo as a source. + +\*\*\*\* + From 3a18856301b8ed3621e61e90a61241cee5fc9bdf Mon Sep 17 00:00:00 2001 From: "Sherif A. Nada" Date: Fri, 6 Nov 2020 01:39:12 -0800 Subject: [PATCH 14/18] Postgres normalization (#831) Make DBT SQL more generic and handle postgres special cases in the macros instead --- .../macros/cross_db_utils/json_operations.sql | 91 +++++++++++-------- .../normalization/destination_type.py | 31 +++++++ .../transform_catalog/transform.py | 50 +++++----- .../destination-bigquery/build.gradle | 1 + .../destination-postgres/build.gradle | 1 + .../destination-snowflake/build.gradle | 1 + .../DefaultNormalizationRunner.java | 2 +- build.gradle | 1 + 8 files changed, 120 insertions(+), 58 deletions(-) create mode 100644 airbyte-integrations/bases/base-normalization/normalization/destination_type.py diff --git a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql index ef7797c92faef..48e2934e5f27d 100644 --- a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql @@ -3,82 +3,101 @@ - Bigquery: JSON_EXTRACT(json_string_expr, json_path_format) -> https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions - Snowflake: JSON_EXTRACT_PATH_TEXT( , '' ) -> https://docs.snowflake.com/en/sql-reference/functions/json_extract_path_text.html - Redshift: json_extract_path_text('json_string', 'path_elem' [,'path_elem'[, …] ] [, null_if_invalid ] ) -> https://docs.aws.amazon.com/redshift/latest/dg/JSON_EXTRACT_PATH_TEXT.html + - Postgres: json_extract_path_text(, 'path' [, 'path' [, ...]]) -> https://www.postgresql.org/docs/12/functions-json.html #} +{# format_path -------------------------------------------------- #} +{% macro format_path(json_path_list) -%} + {{ adapter.dispatch('format_path')(json_path_list) }} +{%- endmacro %} + +{% macro default__format_path(json_path_list) -%} + {{ '.' ~ json_path_list|join('.') }} +{%- endmacro %} + +{% macro postgres__format_path(json_path_list) -%} + {{"'" ~ json_path_list|join("','") ~ "'" }} +{%- endmacro %} + +{% macro bigquery__format_path(json_path_list) -%} + {{'"$' ~ json_path_list|join('"."') ~ '"'}} +{%- endmacro %} + + {# json_extract ------------------------------------------------- #} -{% macro json_extract(json_column, json_path) -%} - {{ adapter.dispatch('json_extract')(json_column, json_path) }} +{% macro json_extract(json_column, json_path_list) -%} + {{ adapter.dispatch('json_extract')(json_column, format_path(json_path_list)) }} {%- endmacro %} -{% macro default__json_extract(json_column, json_path) -%} - json_extract({{json_column}}, {{json_path}}) +{% macro default__json_extract(json_column, json_path_list) -%} + json_extract({{json_column}}, {{ format_path(json_path_list)}}) {%- endmacro %} -{% macro bigquery__json_extract(json_column, json_path) -%} - json_extract({{json_column}}, {{json_path}}) +{% macro bigquery__json_extract(json_column, json_path_list) -%} + json_extract({{json_column}}, {{format_path(json_path_list)}}) {%- endmacro %} -{% macro postgres__json_extract(json_column, json_path) -%} - json_extract_path_text({{json_column}}, {{json_path}}) +{% macro postgres__json_extract(json_column, json_path_list) -%} + jsonb_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) {%- endmacro %} -{% macro redshift__json_extract(json_column, json_path) -%} - json_extract_path_text({{json_column}}, {{json_path}}) +{% macro redshift__json_extract(json_column, json_path_list) -%} + json_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) {%- endmacro %} -{% macro snowflake__json_extract(json_column, json_path) -%} - json_extract_path_text({{json_column}}, {{json_path}}) +{% macro snowflake__json_extract(json_column, json_path_list) -%} + json_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) {%- endmacro %} {# json_extract_scalar ------------------------------------------------- #} -{% macro json_extract_scalar(json_column, json_path) -%} - {{ adapter.dispatch('json_extract_scalar')(json_column, json_path) }} +{% macro json_extract_scalar(json_column, json_path_list) -%} + {{ adapter.dispatch('json_extract_scalar')(json_column, json_path_list) }} {%- endmacro %} -{% macro default__json_extract_scalar(json_column, json_path) -%} - json_extract_scalar({{json_column}}, {{json_path}}) +{% macro default__json_extract_scalar(json_column, json_path_list) -%} + json_extract_scalar({{json_column}}, {{format_path(json_path_list)}}) {%- endmacro %} -{% macro bigquery__json_extract_scalar(json_column, json_path) -%} - json_extract_scalar({{json_column}}, {{json_path}}) +{% macro bigquery__json_extract_scalar(json_column, json_path_list) -%} + json_extract_scalar({{json_column}}, {{format_path(json_path_list)}}) {%- endmacro %} -{% macro postgres__json_extract_scalar(json_column, json_path) -%} - json_extract_path_text({{json_column}}, {{json_path}}) +{% macro postgres__json_extract_scalar(json_column, json_path_list) -%} + jsonb_extract_path_text({{json_column}},{{format_path(json_path_list)}}) {%- endmacro %} -{% macro redshift__json_extract_scalar(json_column, json_path) -%} - json_extract_path_text({{json_column}}, {{json_path}}) +{% macro redshift__json_extract_scalar(json_column, json_path_list) -%} + json_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) {%- endmacro %} -{% macro snowflake__json_extract_scalar(json_column, json_path) -%} - json_extract_path_text({{json_column}}, {{json_path}}) +{% macro snowflake__json_extract_scalar(json_column, json_path_list) -%} + json_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) {%- endmacro %} {# json_extract_array ------------------------------------------------- #} -{% macro json_extract_array(json_column, json_path) -%} - {{ adapter.dispatch('json_extract_array')(json_column, json_path) }} +{% macro json_extract_array(json_column, json_path_list) -%} + {{ adapter.dispatch('json_extract_array')(json_column, json_path_list) }} {%- endmacro %} -{% macro default__json_extract_array(json_column, json_path) -%} - json_extract_array({{json_column}}, {{json_path}}) +{% macro default__json_extract_array(json_column, json_path_list) -%} + json_extract_array({{json_column}}, {{format_path(json_path_list)}}) {%- endmacro %} -{% macro bigquery__json_extract_array(json_column, json_path) -%} - json_extract_array({{json_column}}, {{json_path}}) +{% macro bigquery__json_extract_array(json_column, json_path_list) -%} + json_extract_array({{json_column}}, {{format_path(json_path_list)}}) {%- endmacro %} -{% macro postgres__json_extract_array(json_column, json_path) -%} - json_array_elements(json_extract_path({{json_column}}, {{json_path}})) +{% macro postgres__json_extract_array(json_column, json_path_list) -%} + jsonb_array_elements(jsonb_extract_path({{json_column}},{{format_path(json_path_list)}}) {%- endmacro %} -{% macro redshift__json_extract_array(json_column, json_path) -%} - json_extract_path_text({{json_column}}, {{json_path}}) +{% macro redshift__json_extract_array(json_column, json_path_list) -%} + json_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) {%- endmacro %} -{% macro snowflake__json_extract_array(json_column, json_path) -%} - json_extract_path_text({{json_column}}, {{json_path}}) +{% macro snowflake__json_extract_array(json_column, json_path_list) -%} + json_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) {%- endmacro %} diff --git a/airbyte-integrations/bases/base-normalization/normalization/destination_type.py b/airbyte-integrations/bases/base-normalization/normalization/destination_type.py new file mode 100644 index 0000000000000..fddbfdc6a8c08 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/normalization/destination_type.py @@ -0,0 +1,31 @@ +""" +MIT License + +Copyright (c) 2020 Airbyte + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from enum import Enum + + +class DestinationType(Enum): + bigquery = "bigquery" + postgres = "postgres" + snowflake = "snowflake" diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py index 517c7d14f538c..de2056405de44 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py @@ -25,7 +25,7 @@ import argparse import json import os -from typing import Optional, Set, Tuple, Union +from typing import Dict, List, Optional, Set, Tuple, Union import yaml @@ -58,8 +58,9 @@ def parse(self, args): parser.add_argument("--out", type=str, required=True, help="path to output generated DBT Models to") parser.add_argument("--json-column", type=str, required=False, help="name of the column containing the json blob") parsed_args = parser.parse_args(args) + profiles_yml = read_profiles_yml(parsed_args.profile_config_dir) self.config = { - "schema": extract_schema(parsed_args.profile_config_dir), + "schema": extract_schema(profiles_yml), "catalog": parsed_args.catalog, "output_path": parsed_args.out, "json_column": parsed_args.json_column, @@ -97,14 +98,18 @@ def write_yaml_sources(output: str, schema: str, sources: set) -> None: fh.write(yaml.dump(source_config)) -def extract_schema(profile_dir: str): +def read_profiles_yml(profile_dir: str) -> Dict[str, any]: with open(os.path.join(profile_dir, "profiles.yml"), "r") as file: config = yaml.load(file, Loader=yaml.FullLoader) obj = config["normalize"]["outputs"]["prod"] - if "dataset" in obj: - return obj["dataset"] - else: - return obj["schema"] + return obj + + +def extract_schema(profiles_yml: Dict[str, any]): + if "dataset" in profiles_yml: + return profiles_yml["dataset"] + else: + return profiles_yml["schema"] def read_json_catalog(input_path: str) -> dict: @@ -141,42 +146,42 @@ def find_combining_schema(properties: dict): return set(properties).intersection({"anyOf", "oneOf", "allOf"}) -def json_extract_base_property(path: str, json_col: str, name: str, definition: dict) -> Optional[str]: - current = ".".join([path, name]) +def json_extract_base_property(path: List[str], json_col: str, name: str, definition: dict) -> Optional[str]: + current = path + [name] if "type" not in definition: return None elif is_string(definition["type"]): return ( - f"cast({MACRO_START} json_extract_scalar('{json_col}', \"'{current}'\") " + f"cast({MACRO_START} json_extract_scalar('{json_col}', {current}) " + f"{MACRO_END} as {MACRO_START} dbt_utils.type_string() {MACRO_END}) as {name}" ) elif is_integer(definition["type"]): return ( - f"cast({MACRO_START} json_extract_scalar('{json_col}', \"'{current}'\") " + f"cast({MACRO_START} json_extract_scalar('{json_col}', {current}) " + f"{MACRO_END} as {MACRO_START} dbt_utils.type_int() {MACRO_END}) as {name}" ) elif is_number(definition["type"]): return ( - f"cast({MACRO_START} json_extract_scalar('{json_col}', \"'{current}'\") " + f"cast({MACRO_START} json_extract_scalar('{json_col}', {current}) " + f"{MACRO_END} as {MACRO_START} dbt_utils.type_numeric() {MACRO_END}) as {name}" ) elif is_boolean(definition["type"]): - return f"cast({MACRO_START} json_extract_scalar('{json_col}', \"'{current}'\") {MACRO_END} as boolean) as {name}" + return f"cast({MACRO_START} json_extract_scalar('{json_col}', {current}) {MACRO_END} as boolean) as {name}" else: return None -def json_extract_nested_property(path: str, json_col: str, name: str, definition: dict) -> Union[Tuple[None, None], Tuple[str, str]]: - current = ".".join([path, name]) +def json_extract_nested_property(path: List[str], json_col: str, name: str, definition: dict) -> Union[Tuple[None, None], Tuple[str, str]]: + current = path + [name] if definition is None or "type" not in definition: return None, None elif is_array(definition["type"]): return ( - f"{MACRO_START} json_extract_array('{json_col}', \"'{current}'\") {MACRO_END} as {name}", + f"{MACRO_START} json_extract_array('{json_col}', {current}) {MACRO_END} as {name}", f"cross join {MACRO_START} unnest('{name}') {MACRO_END} as {name}", ) elif is_object(definition["type"]): - return f"{MACRO_START} json_extract('{json_col}', \"'{current}'\") {MACRO_END} as {name}", "" + return f"{MACRO_START} json_extract('{json_col}', {current}) {MACRO_END} as {name}", "" else: return None, None @@ -185,7 +190,7 @@ def select_table(table: str, columns="*"): return f"\nselect {columns} from {table}" -def extract_node_properties(path: str, json_col: str, properties: dict) -> dict: +def extract_node_properties(path: List[str], json_col: str, properties: dict) -> dict: result = {} if properties: for field in properties.keys(): @@ -249,7 +254,9 @@ def extract_nested_properties(path: str, field: str, properties: dict) -> dict: return result -def process_node(path: str, json_col: str, name: str, properties: dict, from_table: str = "", previous="with ", inject_cols="") -> dict: +def process_node( + path: List[str], json_col: str, name: str, properties: dict, from_table: str = "", previous="with ", inject_cols="" +) -> dict: result = {} if previous == "with ": prefix = previous @@ -294,7 +301,7 @@ def process_node(path: str, json_col: str, name: str, properties: dict, from_tab )""" if children_columns[col]: children = process_node( - path="$", + path=[], json_col=col, name=f"{name}_{col}", properties=children_columns[col], @@ -329,7 +336,8 @@ def generate_dbt_model(catalog: dict, json_col: str, schema: str) -> Tuple[dict, properties = {} table = f"{MACRO_START} source('{schema}','{name}') {MACRO_END}" # TODO check if jsonpath are expressed similarly on different databases... (using $?) - result.update(process_node(path="$", json_col=json_col, name=name, properties=properties, from_table=table)) + + result.update(process_node(path=[], json_col=json_col, name=name, properties=properties, from_table=table)) source_tables.add(name) return result, source_tables diff --git a/airbyte-integrations/connectors/destination-bigquery/build.gradle b/airbyte-integrations/connectors/destination-bigquery/build.gradle index 78ab5bbce8300..c602de0651b93 100644 --- a/airbyte-integrations/connectors/destination-bigquery/build.gradle +++ b/airbyte-integrations/connectors/destination-bigquery/build.gradle @@ -24,4 +24,5 @@ application { buildImage.dependsOn(assemble) buildImage.dependsOn(':airbyte-integrations:bases:base-java:buildImage') +buildImage.dependsOn(':airbyte-integrations:bases:base-normalization:buildImage') integrationTest.dependsOn(buildImage) diff --git a/airbyte-integrations/connectors/destination-postgres/build.gradle b/airbyte-integrations/connectors/destination-postgres/build.gradle index da15a5e7a1150..972018ae33887 100644 --- a/airbyte-integrations/connectors/destination-postgres/build.gradle +++ b/airbyte-integrations/connectors/destination-postgres/build.gradle @@ -23,4 +23,5 @@ application { buildImage.dependsOn(assemble) buildImage.dependsOn(':airbyte-integrations:bases:base-java:buildImage') +buildImage.dependsOn(':airbyte-integrations:bases:base-normalization:buildImage') integrationTest.dependsOn(buildImage) diff --git a/airbyte-integrations/connectors/destination-snowflake/build.gradle b/airbyte-integrations/connectors/destination-snowflake/build.gradle index 349e0115a33fc..299263a130464 100644 --- a/airbyte-integrations/connectors/destination-snowflake/build.gradle +++ b/airbyte-integrations/connectors/destination-snowflake/build.gradle @@ -24,5 +24,6 @@ application { } buildImage.dependsOn(assemble) +buildImage.dependsOn(':airbyte-integrations:bases:base-normalization:buildImage') buildImage.dependsOn(':airbyte-integrations:bases:base-java:buildImage') integrationTest.dependsOn(buildImage) diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/normalization/DefaultNormalizationRunner.java b/airbyte-workers/src/main/java/io/airbyte/workers/normalization/DefaultNormalizationRunner.java index 3902cb2f05f67..dd988cc516158 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/normalization/DefaultNormalizationRunner.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/normalization/DefaultNormalizationRunner.java @@ -43,7 +43,7 @@ public class DefaultNormalizationRunner implements NormalizationRunner { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultNormalizationRunner.class); - public static final String NORMALIZATION_IMAGE_NAME = "airbyte/normalization:0.1.0"; + public static final String NORMALIZATION_IMAGE_NAME = "airbyte/normalization:dev"; private final DestinationType destinationType; private final ProcessBuilderFactory pbf; diff --git a/build.gradle b/build.gradle index 632d0c3b1184c..5e932d2cff777 100644 --- a/build.gradle +++ b/build.gradle @@ -49,6 +49,7 @@ def createSpotlessTarget = { pattern -> '.mypy_cache', '.venv', '*.egg-info', + 'dbt-project-template/macros' ] return fileTree(dir: rootDir, include: pattern, exclude: excludes.collect {"**/${it}"}) } From 05d422f1dc1ce8b08796f2f5163e2fe1c2f94df4 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 6 Nov 2020 11:06:36 +0100 Subject: [PATCH 15/18] Changes from code reviews --- .gitignore | 5 +- .../dbt-project-template/.gitignore | 4 + .../macros/cross_db_utils/array.sql | 2 +- .../dbt-project-template/packages.yml | 2 +- .../integration_tests/catalog.json | 464 ------------------ .../integration_tests/one_recipe.json | 73 --- .../transform_catalog/transform.py | 20 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- build.gradle | 4 +- 11 files changed, 22 insertions(+), 558 deletions(-) create mode 100644 airbyte-integrations/bases/base-normalization/dbt-project-template/.gitignore delete mode 100644 airbyte-integrations/bases/base-normalization/integration_tests/catalog.json delete mode 100644 airbyte-integrations/bases/base-normalization/integration_tests/one_recipe.json diff --git a/.gitignore b/.gitignore index ad7a98c6dfc37..696d67415307e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,4 @@ __pycache__ .mypy_cache # dbt -profiles.yml -airbyte-integrations/bases/base-normalization/dbt-project-template/logs/ -airbyte-integrations/bases/base-normalization/dbt-project-template/models/generated -airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_modules/ +profiles.yml \ No newline at end of file diff --git a/airbyte-integrations/bases/base-normalization/dbt-project-template/.gitignore b/airbyte-integrations/bases/base-normalization/dbt-project-template/.gitignore new file mode 100644 index 0000000000000..f2f1a9f2ad9fb --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/.gitignore @@ -0,0 +1,4 @@ +build/ +logs/ +models/generated/ +dbt_modules/ diff --git a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql index b02d80994e161..4c9e5adf8a329 100644 --- a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql @@ -30,4 +30,4 @@ {% macro snowflake__unnest(array_col) -%} -- TODO test this!! not so sure yet... table(flatten({{ array_col }})) -{%- endmacro %} \ No newline at end of file +{%- endmacro %} diff --git a/airbyte-integrations/bases/base-normalization/dbt-project-template/packages.yml b/airbyte-integrations/bases/base-normalization/dbt-project-template/packages.yml index e73e9a48e6184..426ea3d64ea9b 100755 --- a/airbyte-integrations/bases/base-normalization/dbt-project-template/packages.yml +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/packages.yml @@ -2,4 +2,4 @@ packages: - git: "https://github.com/fishtown-analytics/dbt-utils.git" - revision: 0.6.2 \ No newline at end of file + revision: 0.6.2 diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json b/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json deleted file mode 100644 index 6a2eee4856e6c..0000000000000 --- a/airbyte-integrations/bases/base-normalization/integration_tests/catalog.json +++ /dev/null @@ -1,464 +0,0 @@ -{ - "streams": [ - { - "name": "recipes_json", - "json_schema": { - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "http://example.com/example.json", - "type": "object", - "title": "The root schema", - "description": "The root schema comprises the entire JSON document.", - "default": {}, - "examples": [ - { - "Name": "Christmas pie", - "url": "https://www.bbcgoodfood.com/recipes/2793/christmas-pie", - "Description": "Combine a few key Christmas flavours here to make a pie that both children and adults will adore", - "Author": "Mary Cadogan", - "Ingredients": [ - { - "quantity": "2 tbsp", - "ingredient": "olive oil", - "nutritionfacts": { - "calories": 119, - "fat": "13.5g" - } - }, - { - "quantity": "knob", - "ingredient": "butter", - "nutritionfacts": { - "calories": 102, - "fat": "11.5g" - } - }, - { - "quantity": "1", - "ingredient": "onion", - "preparation": "finely chopped", - "nutritionfacts": { - "calories": 102, - "fat": "11.5g" - } - }, - { - "quantity": "500g", - "ingredient": "sausagemeat or skinned sausages", - "nutritionfacts": { - "calories": 268, - "fat": "18g" - } - }, - { - "quantity": "1", - "ingredient": "lemon", - "preparation": "grated zest", - "nutritionfacts": { - "calories": 13 - } - }, - { - "quantity": "100g", - "ingredient": "fresh white breadcrumbs" - }, - { - "quantity": "85g", - "ingredient": "ready-to-eat dried apricots", - "preparation": "chopped", - "nutritionfacts": { - "calories": 34, - "fat": "0.27g" - } - }, - { - "quantity": "58g", - "ingredient": "chestnut, canned or vacuum-packed", - "preparation": "chopped", - "nutritionfacts": { - "calories": 77, - "fat": "1g" - } - }, - { - "quantity": "2 tsp", - "ingredient": "fresh or dried thyme", - "preparation": "chopped" - }, - { - "quantity": "100g", - "ingredient": "cranberries, fresh or frozen" - }, - { - "quantity": "500g", - "ingredient": "boneless, skinless chicken breasts", - "nutritionfacts": { - "calories": 284, - "fat": "6.2g" - } - }, - { - "quantity": "500g", - "ingredient": "pack ready-made shortcrust pastry" - }, - { - "quantity": "1", - "ingredient": "beaten egg", - "preparation": "to glaze", - "nutritionfacts": { - "calories": 75, - "fat": "5g" - } - } - ], - "Method": [ - "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", - "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.", - "Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.", - "Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.", - "Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles." - ] - } - ], - "required": [ - "Name", - "url", - "Description", - "Author", - "Ingredients", - "Method" - ], - "properties": { - "Name": { - "$id": "#/properties/Name", - "type": "string", - "title": "The Name schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["Christmas pie"] - }, - "url": { - "$id": "#/properties/url", - "type": "string", - "title": "The url schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "https://www.bbcgoodfood.com/recipes/2793/christmas-pie" - ] - }, - "Description": { - "$id": "#/properties/Description", - "type": "string", - "title": "The Description schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "Combine a few key Christmas flavours here to make a pie that both children and adults will adore" - ] - }, - "Author": { - "$id": "#/properties/Author", - "type": "string", - "title": "The Author schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["Mary Cadogan"] - }, - "Ingredients": { - "$id": "#/properties/Ingredients", - "type": "array", - "title": "The Ingredients schema", - "description": "An explanation about the purpose of this instance.", - "default": [], - "examples": [ - [ - { - "quantity": "2 tbsp", - "ingredient": "olive oil", - "nutritionfacts": { - "calories": 119, - "fat": "13.5g" - } - }, - { - "quantity": "knob", - "ingredient": "butter", - "nutritionfacts": { - "calories": 102, - "fat": "11.5g" - } - } - ] - ], - "additionalItems": true, - "items": { - "$id": "#/properties/Ingredients/items", - "anyOf": [ - { - "$id": "#/properties/Ingredients/items/anyOf/0", - "type": "object", - "title": "The first anyOf schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "examples": [ - { - "quantity": "2 tbsp", - "ingredient": "olive oil", - "nutritionfacts": { - "calories": 119, - "fat": "13.5g" - } - } - ], - "required": ["quantity", "ingredient", "nutritionfacts"], - "properties": { - "quantity": { - "$id": "#/properties/Ingredients/items/anyOf/0/properties/quantity", - "type": "string", - "title": "The quantity schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["2 tbsp"] - }, - "ingredient": { - "$id": "#/properties/Ingredients/items/anyOf/0/properties/ingredient", - "type": "string", - "title": "The ingredient schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["olive oil"] - }, - "nutritionfacts": { - "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts", - "type": "object", - "title": "The nutritionfacts schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "examples": [ - { - "calories": 119, - "fat": "13.5g" - } - ], - "required": ["calories", "fat"], - "properties": { - "calories": { - "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/calories", - "type": "integer", - "title": "The calories schema", - "description": "An explanation about the purpose of this instance.", - "default": 0, - "examples": [119] - }, - "fat": { - "$id": "#/properties/Ingredients/items/anyOf/0/properties/nutritionfacts/properties/fat", - "type": "string", - "title": "The fat schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["13.5g"] - } - }, - "additionalProperties": true - } - }, - "additionalProperties": true - }, - { - "$id": "#/properties/Ingredients/items/anyOf/1", - "type": "object", - "title": "The second anyOf schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "examples": [ - { - "quantity": "1", - "ingredient": "onion", - "preparation": "finely chopped", - "nutritionfacts": { - "calories": 102, - "fat": "11.5g" - } - } - ], - "required": [ - "quantity", - "ingredient", - "preparation", - "nutritionfacts" - ], - "properties": { - "quantity": { - "$id": "#/properties/Ingredients/items/anyOf/1/properties/quantity", - "type": "string", - "title": "The quantity schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["1"] - }, - "ingredient": { - "$id": "#/properties/Ingredients/items/anyOf/1/properties/ingredient", - "type": "string", - "title": "The ingredient schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["onion"] - }, - "preparation": { - "$id": "#/properties/Ingredients/items/anyOf/1/properties/preparation", - "type": "string", - "title": "The preparation schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["finely chopped"] - }, - "nutritionfacts": { - "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts", - "type": "object", - "title": "The nutritionfacts schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "examples": [ - { - "calories": 102, - "fat": "11.5g" - } - ], - "required": ["calories", "fat"], - "properties": { - "calories": { - "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/calories", - "type": "integer", - "title": "The calories schema", - "description": "An explanation about the purpose of this instance.", - "default": 0, - "examples": [102] - }, - "fat": { - "$id": "#/properties/Ingredients/items/anyOf/1/properties/nutritionfacts/properties/fat", - "type": "string", - "title": "The fat schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["11.5g"] - } - }, - "additionalProperties": true - } - }, - "additionalProperties": true - }, - { - "$id": "#/properties/Ingredients/items/anyOf/2", - "type": "object", - "title": "The third anyOf schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "examples": [ - { - "quantity": "100g", - "ingredient": "fresh white breadcrumbs" - } - ], - "required": ["quantity", "ingredient"], - "properties": { - "quantity": { - "$id": "#/properties/Ingredients/items/anyOf/2/properties/quantity", - "type": "string", - "title": "The quantity schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["100g"] - }, - "ingredient": { - "$id": "#/properties/Ingredients/items/anyOf/2/properties/ingredient", - "type": "string", - "title": "The ingredient schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["fresh white breadcrumbs"] - } - }, - "additionalProperties": true - }, - { - "$id": "#/properties/Ingredients/items/anyOf/3", - "type": "object", - "title": "The fourth anyOf schema", - "description": "An explanation about the purpose of this instance.", - "default": {}, - "examples": [ - { - "quantity": "2 tsp", - "ingredient": "fresh or dried thyme", - "preparation": "chopped" - } - ], - "required": ["quantity", "ingredient", "preparation"], - "properties": { - "quantity": { - "$id": "#/properties/Ingredients/items/anyOf/3/properties/quantity", - "type": "string", - "title": "The quantity schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["2 tsp"] - }, - "ingredient": { - "$id": "#/properties/Ingredients/items/anyOf/3/properties/ingredient", - "type": "string", - "title": "The ingredient schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["fresh or dried thyme"] - }, - "preparation": { - "$id": "#/properties/Ingredients/items/anyOf/3/properties/preparation", - "type": "string", - "title": "The preparation schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": ["chopped"] - } - }, - "additionalProperties": true - } - ] - } - }, - "Method": { - "$id": "#/properties/Method", - "type": "array", - "title": "The Method schema", - "description": "An explanation about the purpose of this instance.", - "default": [], - "examples": [ - [ - "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", - "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins." - ] - ], - "additionalItems": true, - "items": { - "$id": "#/properties/Method/items", - "anyOf": [ - { - "$id": "#/properties/Method/items/anyOf/0", - "type": "string", - "title": "The first anyOf schema", - "description": "An explanation about the purpose of this instance.", - "default": "", - "examples": [ - "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", - "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins." - ] - } - ] - } - } - }, - "additionalProperties": true - } - } - ] -} diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/one_recipe.json b/airbyte-integrations/bases/base-normalization/integration_tests/one_recipe.json deleted file mode 100644 index efcba70869d5e..0000000000000 --- a/airbyte-integrations/bases/base-normalization/integration_tests/one_recipe.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "Name": "Christmas pie", - "url": "https://www.bbcgoodfood.com/recipes/2793/christmas-pie", - "Description": "Combine a few key Christmas flavours here to make a pie that both children and adults will adore", - "Author": "Mary Cadogan", - "Ingredients": [ - { - "quantity": "2 tbsp", - "ingredient": "olive oil", - "nutritionfacts": { "calories": 119, "fat": "13.5g" } - }, - { - "quantity": "knob", - "ingredient": "butter", - "nutritionfacts": { "calories": 102, "fat": "11.5g" } - }, - { - "quantity": "1", - "ingredient": "onion", - "preparation": "finely chopped", - "nutritionfacts": { "calories": 102, "fat": "11.5g" } - }, - { - "quantity": "500g", - "ingredient": "sausagemeat or skinned sausages", - "nutritionfacts": { "calories": 268, "fat": "18g" } - }, - { - "quantity": "1", - "ingredient": "lemon", - "preparation": "grated zest", - "nutritionfacts": { "calories": 13 } - }, - { "quantity": "100g", "ingredient": "fresh white breadcrumbs" }, - { - "quantity": "85g", - "ingredient": "ready-to-eat dried apricots", - "preparation": "chopped", - "nutritionfacts": { "calories": 34, "fat": "0.27g" } - }, - { - "quantity": "58g", - "ingredient": "chestnut, canned or vacuum-packed", - "preparation": "chopped", - "nutritionfacts": { "calories": 77, "fat": "1g" } - }, - { - "quantity": "2 tsp", - "ingredient": "fresh or dried thyme", - "preparation": "chopped" - }, - { "quantity": "100g", "ingredient": "cranberries, fresh or frozen" }, - { - "quantity": "500g", - "ingredient": "boneless, skinless chicken breasts", - "nutritionfacts": { "calories": 284, "fat": "6.2g" } - }, - { "quantity": "500g", "ingredient": "pack ready-made shortcrust pastry" }, - { - "quantity": "1", - "ingredient": "beaten egg", - "preparation": "to glaze", - "nutritionfacts": { "calories": 75, "fat": "5g" } - } - ], - "Method": [ - "Heat oven to 190C/fan 170C/gas 5. Heat 1 tbsp oil and the butter in a frying pan, then add the onion and fry for 5 mins until softened. Cool slightly. Tip the sausagemeat, lemon zest, breadcrumbs, apricots, chestnuts and thyme into a bowl. Add the onion and cranberries, and mix everything together with your hands, adding plenty of pepper and a little salt.", - "Cut each chicken breast into three fillets lengthwise and season all over with salt and pepper. Heat the remaining oil in the frying pan, and fry the chicken fillets quickly until browned, about 6-8 mins.", - "Roll out two-thirds of the pastry to line a 20-23cm springform or deep loose-based tart tin. Press in half the sausage mix and spread to level. Then add the chicken pieces in one layer and cover with the rest of the sausage. Press down lightly.", - "Roll out the remaining pastry. Brush the edges of the pastry with beaten egg and cover with the pastry lid. Pinch the edges to seal, then trim. Brush the top of the pie with egg, then roll out the trimmings to make holly leaf shapes and berries. Decorate the pie and brush again with egg.", - "Set the tin on a baking sheet and bake for 50-60 mins, then cool in the tin for 15 mins. Remove and leave to cool completely. Serve with a winter salad and pickles." - ] -} diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py index de2056405de44..37929ef9f7eaa 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py @@ -25,7 +25,7 @@ import argparse import json import os -from typing import Dict, List, Optional, Set, Tuple, Union +from typing import List, Optional, Set, Tuple, Union import yaml @@ -98,14 +98,14 @@ def write_yaml_sources(output: str, schema: str, sources: set) -> None: fh.write(yaml.dump(source_config)) -def read_profiles_yml(profile_dir: str) -> Dict[str, any]: +def read_profiles_yml(profile_dir: str) -> dict: with open(os.path.join(profile_dir, "profiles.yml"), "r") as file: config = yaml.load(file, Loader=yaml.FullLoader) obj = config["normalize"]["outputs"]["prod"] return obj -def extract_schema(profiles_yml: Dict[str, any]): +def extract_schema(profiles_yml: dict) -> str: if "dataset" in profiles_yml: return profiles_yml["dataset"] else: @@ -200,7 +200,7 @@ def extract_node_properties(path: List[str], json_col: str, properties: dict) -> return result -def find_properties_object(path: str, field: str, properties) -> dict: +def find_properties_object(path: List[str], field: str, properties) -> dict: if isinstance(properties, str) or isinstance(properties, int): return {} else: @@ -226,7 +226,7 @@ def find_properties_object(path: str, field: str, properties) -> dict: return {} -def extract_nested_properties(path: str, field: str, properties: dict) -> dict: +def extract_nested_properties(path: List[str], field: str, properties: dict) -> dict: result = {} if properties: for key in properties.keys(): @@ -234,7 +234,7 @@ def extract_nested_properties(path: str, field: str, properties: dict) -> dict: if combining: # skip combining schemas for combo in combining: - found = find_properties_object(path=f"{path}.{field}.{key}", field=key, properties=properties[key][combo]) + found = find_properties_object(path=path + [field, key], field=key, properties=properties[key][combo]) result.update(found) elif "type" not in properties[key]: pass @@ -243,13 +243,13 @@ def extract_nested_properties(path: str, field: str, properties: dict) -> dict: if combining: # skip combining schemas for combo in combining: - found = find_properties_object(path=f"{path}.{key}", field=key, properties=properties[key]["items"][combo]) + found = find_properties_object(path=path + [field, key], field=key, properties=properties[key]["items"][combo]) result.update(found) else: - found = find_properties_object(path=f"{path}.{key}", field=key, properties=properties[key]["items"]) + found = find_properties_object(path=path + [field, key], field=key, properties=properties[key]["items"]) result.update(found) elif is_object(properties[key]["type"]): - found = find_properties_object(path=f"{path}.{key}", field=key, properties=properties[key]) + found = find_properties_object(path=path + [field, key], field=key, properties=properties[key]) result.update(found) return result @@ -335,8 +335,6 @@ def generate_dbt_model(catalog: dict, json_col: str, schema: str) -> Tuple[dict, else: properties = {} table = f"{MACRO_START} source('{schema}','{name}') {MACRO_END}" - # TODO check if jsonpath are expressed similarly on different databases... (using $?) - result.update(process_node(path=[], json_col=json_col, name=name, properties=properties, from_table=table)) source_tables.add(name) return result, source_tables diff --git a/airbyte-integrations/connectors/destination-bigquery/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-bigquery/src/main/resources/spec.json index 82acabeb844f4..06c279bf4ddf2 100644 --- a/airbyte-integrations/connectors/destination-bigquery/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-bigquery/src/main/resources/spec.json @@ -23,7 +23,7 @@ "type": "boolean", "default": false, "description": "Whether or not to normalize the data in the destination. See basic normalization for more details.", - "examples": ["false"] + "examples": [true, false] } } } diff --git a/airbyte-integrations/connectors/destination-postgres/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-postgres/src/main/resources/spec.json index 2b0781889ebeb..6b83d3f406d3d 100644 --- a/airbyte-integrations/connectors/destination-postgres/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-postgres/src/main/resources/spec.json @@ -41,7 +41,7 @@ "type": "boolean", "default": false, "description": "Whether or not to normalize the data in the destination. See basic normalization for more details.", - "examples": ["false"] + "examples": [true, false] } } } diff --git a/airbyte-integrations/connectors/destination-snowflake/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-snowflake/src/main/resources/spec.json index db8431ae5b519..7b6e4ebfb80ab 100644 --- a/airbyte-integrations/connectors/destination-snowflake/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-snowflake/src/main/resources/spec.json @@ -59,7 +59,7 @@ "type": "boolean", "default": false, "description": "Whether or not to normalize the data in the destination. See basic normalization for more details.", - "examples": ["false"] + "examples": [true, false] } } } diff --git a/build.gradle b/build.gradle index 5e932d2cff777..f45285912ea0a 100644 --- a/build.gradle +++ b/build.gradle @@ -49,7 +49,9 @@ def createSpotlessTarget = { pattern -> '.mypy_cache', '.venv', '*.egg-info', - 'dbt-project-template/macros' + 'dbt-project-template/macros', + 'dbt-project-template/models', + 'dbt-project-template/dbt_modules' ] return fileTree(dir: rootDir, include: pattern, exclude: excludes.collect {"**/${it}"}) } From fb32f13be5ba8f2ca2030eec039f56fd36b8fbf2 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 6 Nov 2020 13:09:05 +0100 Subject: [PATCH 16/18] Handle Quoting in generated SQL so it can be configured as needed per destination warehouses --- .../dbt-project-template/dbt_project.yml | 14 ++-- .../macros/cross_db_utils/array.sql | 9 +-- .../macros/cross_db_utils/json_operations.sql | 55 ++++++++------- .../transform_catalog/transform.py | 69 +++++++++++++------ 4 files changed, 90 insertions(+), 57 deletions(-) diff --git a/airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_project.yml b/airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_project.yml index 9a1586c77621e..bfb029a83c829 100755 --- a/airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_project.yml +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/dbt_project.yml @@ -24,17 +24,17 @@ clean-targets: # directories to be removed by `dbt clean` - "build" - "dbt_modules" +# https://docs.getdbt.com/reference/project-configs/quoting/ +quoting: + database: true + schema: true + identifier: true + # You can define configurations for models in the `source-paths` directory here. # Using these configurations, you can enable or disable models, change how they # are materialized, and more! models: airbyte: # Schema (dataset) defined in profiles.yml is concatenated with schema below for dbt's final output - +schema: normalized + +schema: NORMALIZED +materialized: view - -# https://docs.getdbt.com/reference/project-configs/quoting/ -quoting: - database: true - schema: true - identifier: true diff --git a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql index 4c9e5adf8a329..e6ce1cb4043e4 100644 --- a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql @@ -3,6 +3,7 @@ - Bigquery: unnest() -> https://cloud.google.com/bigquery/docs/reference/standard-sql/arrays#flattening-arrays-and-repeated-fields - Snowflake: flatten() -> https://docs.snowflake.com/en/sql-reference/functions/flatten.html - Redshift: -> https://blog.getdbt.com/how-to-unnest-arrays-in-redshift/ + - postgres: unnest() -> https://www.postgresqltutorial.com/postgresql-array/ #} {# flatten ------------------------------------------------- #} @@ -12,15 +13,15 @@ {%- endmacro %} {% macro default__unnest(array_col) -%} - unnest({{ array_col }}) + unnest({{ adapter.quote_as_configured(array_col, 'identifier')|trim }}) {%- endmacro %} {% macro bigquery__unnest(array_col) -%} - unnest({{ array_col }}) + unnest({{ adapter.quote_as_configured(array_col, 'identifier')|trim }}) {%- endmacro %} {% macro postgres__unnest(array_col) -%} - unnest({{ array_col }}) + unnest({{ adapter.quote_as_configured(array_col, 'identifier')|trim }}) {%- endmacro %} {% macro redshift__unnest(array_col) -%} @@ -29,5 +30,5 @@ {% macro snowflake__unnest(array_col) -%} -- TODO test this!! not so sure yet... - table(flatten({{ array_col }})) + table(flatten({{ adapter.quote_as_configured(array_col, 'identifier')|trim }})) {%- endmacro %} diff --git a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql index 48e2934e5f27d..81013939818a5 100644 --- a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/json_operations.sql @@ -6,48 +6,55 @@ - Postgres: json_extract_path_text(, 'path' [, 'path' [, ...]]) -> https://www.postgresql.org/docs/12/functions-json.html #} -{# format_path -------------------------------------------------- #} -{% macro format_path(json_path_list) -%} - {{ adapter.dispatch('format_path')(json_path_list) }} +{# format_json_path -------------------------------------------------- #} +{% macro format_json_path(json_path_list) -%} + {{ adapter.dispatch('format_json_path')(json_path_list) }} {%- endmacro %} -{% macro default__format_path(json_path_list) -%} +{% macro default__format_json_path(json_path_list) -%} {{ '.' ~ json_path_list|join('.') }} {%- endmacro %} -{% macro postgres__format_path(json_path_list) -%} - {{"'" ~ json_path_list|join("','") ~ "'" }} +{% macro bigquery__format_json_path(json_path_list) -%} + {{ '"$.' ~ json_path_list|join('"."') ~ '"' }} {%- endmacro %} -{% macro bigquery__format_path(json_path_list) -%} - {{'"$' ~ json_path_list|join('"."') ~ '"'}} +{% macro postgres__format_json_path(json_path_list) -%} + {{ "'" ~ json_path_list|join("','") ~ "'" }} {%- endmacro %} +{% macro redshift__format_json_path(json_path_list) -%} + {{ "'" ~ json_path_list|join("','") ~ "'" }} +{%- endmacro %} + +{% macro snowflake__format_json_path(json_path_list) -%} + {{ "'" ~ json_path_list|join('"."') ~ "'" }} +{%- endmacro %} {# json_extract ------------------------------------------------- #} {% macro json_extract(json_column, json_path_list) -%} - {{ adapter.dispatch('json_extract')(json_column, format_path(json_path_list)) }} + {{ adapter.dispatch('json_extract')(json_column, json_path_list) }} {%- endmacro %} {% macro default__json_extract(json_column, json_path_list) -%} - json_extract({{json_column}}, {{ format_path(json_path_list)}}) + json_extract({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} {% macro bigquery__json_extract(json_column, json_path_list) -%} - json_extract({{json_column}}, {{format_path(json_path_list)}}) + json_extract({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} {% macro postgres__json_extract(json_column, json_path_list) -%} - jsonb_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) + jsonb_extract_path_text({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} {% macro redshift__json_extract(json_column, json_path_list) -%} - json_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) + json_extract_path_text({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} {% macro snowflake__json_extract(json_column, json_path_list) -%} - json_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) + json_extract_path_text({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} {# json_extract_scalar ------------------------------------------------- #} @@ -57,23 +64,23 @@ {%- endmacro %} {% macro default__json_extract_scalar(json_column, json_path_list) -%} - json_extract_scalar({{json_column}}, {{format_path(json_path_list)}}) + json_extract_scalar({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} {% macro bigquery__json_extract_scalar(json_column, json_path_list) -%} - json_extract_scalar({{json_column}}, {{format_path(json_path_list)}}) + json_extract_scalar({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} {% macro postgres__json_extract_scalar(json_column, json_path_list) -%} - jsonb_extract_path_text({{json_column}},{{format_path(json_path_list)}}) + jsonb_extract_path_text({{ adapter.quote_as_configured(json_column, 'identifier')|trim }},{{ format_json_path(json_path_list) }}) {%- endmacro %} {% macro redshift__json_extract_scalar(json_column, json_path_list) -%} - json_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) + json_extract_path_text({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} {% macro snowflake__json_extract_scalar(json_column, json_path_list) -%} - json_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) + json_extract_path_text({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} {# json_extract_array ------------------------------------------------- #} @@ -83,21 +90,21 @@ {%- endmacro %} {% macro default__json_extract_array(json_column, json_path_list) -%} - json_extract_array({{json_column}}, {{format_path(json_path_list)}}) + json_extract_array({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} {% macro bigquery__json_extract_array(json_column, json_path_list) -%} - json_extract_array({{json_column}}, {{format_path(json_path_list)}}) + json_extract_array({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} {% macro postgres__json_extract_array(json_column, json_path_list) -%} - jsonb_array_elements(jsonb_extract_path({{json_column}},{{format_path(json_path_list)}}) + jsonb_array_elements(jsonb_extract_path({{ adapter.quote_as_configured(json_column, 'identifier')|trim }},{{ format_json_path(json_path_list) }}) {%- endmacro %} {% macro redshift__json_extract_array(json_column, json_path_list) -%} - json_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) + json_extract_path_text({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} {% macro snowflake__json_extract_array(json_column, json_path_list) -%} - json_extract_path_text({{json_column}}, {{format_path(json_path_list)}}) + json_extract_path_text({{ adapter.quote_as_configured(json_column, 'identifier')|trim }}, {{ format_json_path(json_path_list) }}) {%- endmacro %} diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py index 37929ef9f7eaa..a48fadc9f2b08 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py @@ -47,11 +47,11 @@ class TransformCatalog: config: dict = {} - def run(self, args): + def run(self, args) -> None: self.parse(args) self.process_catalog() - def parse(self, args): + def parse(self, args) -> None: parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--profile-config-dir", type=str, required=True, help="path to directory containing DBT profiles.yml") parser.add_argument("--catalog", nargs="+", type=str, required=True, help="path to Catalog (JSON Schema) file") @@ -66,7 +66,7 @@ def parse(self, args): "json_column": parsed_args.json_column, } - def process_catalog(self): + def process_catalog(self) -> None: source_tables: set = set() schema = self.config["schema"] output = self.config["output_path"] @@ -91,7 +91,23 @@ def output_sql_models(output: str, result: dict) -> None: @staticmethod def write_yaml_sources(output: str, schema: str, sources: set) -> None: tables = [{"name": source} for source in sources] - source_config = {"version": 2, "sources": [{"name": schema, "tables": tables}]} + source_config = { + "version": 2, + "sources": [ + { + "name": schema, + "tables": tables, + "quoting": { + "database": True, + "schema": True, + "identifier": True, + }, + }, + ], + } + # Quoting options are hardcoded and passed down to the sources instead of + # inheriting them from dbt_project.yml (does not work well for some reasons?) + # Apparently, Snowflake needs this quoting configuration to work properly... source_path = os.path.join(output, "sources.yml") if not os.path.exists(source_path): with open(source_path, "w") as fh: @@ -142,7 +158,7 @@ def is_object(property_type) -> bool: return property_type == "object" or "object" in property_type -def find_combining_schema(properties: dict): +def find_combining_schema(properties: dict) -> set: return set(properties).intersection({"anyOf", "oneOf", "allOf"}) @@ -152,21 +168,24 @@ def json_extract_base_property(path: List[str], json_col: str, name: str, defini return None elif is_string(definition["type"]): return ( - f"cast({MACRO_START} json_extract_scalar('{json_col}', {current}) " - + f"{MACRO_END} as {MACRO_START} dbt_utils.type_string() {MACRO_END}) as {name}" + f"cast({MACRO_START} json_extract_scalar('{json_col}', {current}) {MACRO_END} as {MACRO_START} dbt_utils.type_string()" + + f" {MACRO_END}) as {MACRO_START} adapter.quote_as_configured('{name}', 'identifier') {MACRO_END}" ) elif is_integer(definition["type"]): return ( - f"cast({MACRO_START} json_extract_scalar('{json_col}', {current}) " - + f"{MACRO_END} as {MACRO_START} dbt_utils.type_int() {MACRO_END}) as {name}" + f"cast({MACRO_START} json_extract_scalar('{json_col}', {current}) {MACRO_END} as {MACRO_START} dbt_utils.type_int()" + + f" {MACRO_END}) as {MACRO_START} adapter.quote_as_configured('{name}', 'identifier') {MACRO_END}" ) elif is_number(definition["type"]): return ( - f"cast({MACRO_START} json_extract_scalar('{json_col}', {current}) " - + f"{MACRO_END} as {MACRO_START} dbt_utils.type_numeric() {MACRO_END}) as {name}" + f"cast({MACRO_START} json_extract_scalar('{json_col}', {current}) {MACRO_END} as {MACRO_START} dbt_utils.type_numeric()" + + f" {MACRO_END}) as {MACRO_START} adapter.quote_as_configured('{name}', 'identifier') {MACRO_END}" ) elif is_boolean(definition["type"]): - return f"cast({MACRO_START} json_extract_scalar('{json_col}', {current}) {MACRO_END} as boolean) as {name}" + return ( + f"cast({MACRO_START} json_extract_scalar('{json_col}', {current}) {MACRO_END} as boolean" + + f") as {MACRO_START} adapter.quote_as_configured('{name}', 'identifier') {MACRO_END}" + ) else: return None @@ -177,11 +196,17 @@ def json_extract_nested_property(path: List[str], json_col: str, name: str, defi return None, None elif is_array(definition["type"]): return ( - f"{MACRO_START} json_extract_array('{json_col}', {current}) {MACRO_END} as {name}", - f"cross join {MACRO_START} unnest('{name}') {MACRO_END} as {name}", + f"{MACRO_START} json_extract_array('{json_col}', {current}) {MACRO_END} as " + + f"{MACRO_START} adapter.quote_as_configured('{name}', 'identifier') {MACRO_END}", + f"cross join {MACRO_START} unnest('{name}') {MACRO_END} as " + + f"{MACRO_START} adapter.quote_as_configured('{name}', 'identifier') {MACRO_END}", ) elif is_object(definition["type"]): - return f"{MACRO_START} json_extract('{json_col}', {current}) {MACRO_END} as {name}", "" + return ( + f"{MACRO_START} json_extract('{json_col}', {current}) {MACRO_END} as " + + f"{MACRO_START} adapter.quote_as_configured('{name}', 'identifier') {MACRO_END}", + "", + ) else: return None, None @@ -264,8 +289,8 @@ def process_node( prefix = previous + "," node_properties = extract_node_properties(path=path, json_col=json_col, properties=properties) node_columns = ",\n ".join([sql for sql in node_properties.values()]) - hash_node_columns = ", ".join([f'"{column}"' for column in node_properties.keys()]) - hash_node_columns = f"{MACRO_START} dbt_utils.surrogate_key([{hash_node_columns}]) {MACRO_END}" + hash_node_columns = ",\n ".join([f"adapter.quote_as_configured('{column}', 'identifier')" for column in node_properties.keys()]) + hash_node_columns = f"{MACRO_START} dbt_utils.surrogate_key([\n {hash_node_columns}\n ]) {MACRO_END}" node_sql = f"""{prefix} {name}_node as ( select {inject_cols} @@ -275,7 +300,7 @@ def process_node( {name}_with_id as ( select *, - {hash_node_columns} as _{name}_hashid + {hash_node_columns} as {MACRO_START} adapter.quote_as_configured('_{name}_hashid', 'identifier') {MACRO_END} from {name}_node )""" # SQL Query for current node's basic properties @@ -294,8 +319,8 @@ def process_node( ), {name}_with_id as ( select - {hash_node_columns} as _{name}_hashid, - {col} + {hash_node_columns} as {MACRO_START} adapter.quote_as_configured('_{name}_hashid', 'identifier') {MACRO_END}, + {MACRO_START} adapter.quote_as_configured('{col}', 'identifier') {MACRO_END} from {name}_node {join_child_table} )""" @@ -307,7 +332,7 @@ def process_node( properties=children_columns[col], from_table=f"{name}_with_id", previous=child_sql, - inject_cols=f"\n _{name}_hashid as _{name}_foreign_hashid,", + inject_cols=f"\n {MACRO_START} adapter.quote_as_configured('_{name}_hashid', 'identifier') {MACRO_END} as _{name}_foreign_hashid,", ) result.update(children) else: @@ -315,7 +340,7 @@ def process_node( result[f"{name}_{col}"] = child_sql + select_table( f"{name}_with_id", columns=f""" - _{name}_hashid as _{name}_foreign_hashid, + {MACRO_START} adapter.quote_as_configured('_{name}_hashid', 'identifier') {MACRO_END} as _{name}_foreign_hashid, {col} """, ) From e6cc9b1cf2b7ef14207a8e7add45020b4e64c8ed Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 6 Nov 2020 22:36:39 +0100 Subject: [PATCH 17/18] Check if items is in dict before accessing it --- .../normalization/transform_catalog/transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py index a48fadc9f2b08..e9dc1fcab099b 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/transform.py @@ -263,7 +263,7 @@ def extract_nested_properties(path: List[str], field: str, properties: dict) -> result.update(found) elif "type" not in properties[key]: pass - elif is_array(properties[key]["type"]): + elif is_array(properties[key]["type"]) and "items" in properties[key]: combining = find_combining_schema(properties[key]["items"]) if combining: # skip combining schemas From 9904b29e40e45f20e8ce88a1d9f2f3672d517b13 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 6 Nov 2020 22:48:00 +0100 Subject: [PATCH 18/18] It works on snowflake!! --- .../dbt-project-template/macros/cross_db_utils/array.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql index e6ce1cb4043e4..b3bb96a703c9d 100644 --- a/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql +++ b/airbyte-integrations/bases/base-normalization/dbt-project-template/macros/cross_db_utils/array.sql @@ -29,6 +29,5 @@ {%- endmacro %} {% macro snowflake__unnest(array_col) -%} - -- TODO test this!! not so sure yet... table(flatten({{ adapter.quote_as_configured(array_col, 'identifier')|trim }})) {%- endmacro %}