{% docs macro_address_to_coordinates %} # Procedure to define E-UDF in GCP and calling the function in DBT ## 1.0 Information required to create E-UDF As you complete the tasks to create an external function in the Google Cloud Console, you are required to record specific values (e.g. Cloud Function Trigger URL) during each step in the process. Whereby, the values you entered, will be required in subsequent steps. To facilitate recording/tracking of this information, we have provided a template with fields for each of the required values. Save the template in a text editor for easier referencing. Reference Source: [Snowflake Planning](https://docs.snowflake.com/en/sql-reference/external-functions-creating-gcp-planning) ``` =================== Tracking Worksehet: Google Cloud Console =================== Step 1: Cloud Function (Remote Service) Info Cloud Function Trigger URL: ____________________________________________________ Step 2: API Config File Info Path Suffix: ___________________________________________________________________ Configuration File Name: _______________________________________________________ Step 3: API Gateway (Proxy Service) Info Managed Service Identifier: ____________________________________________________ Gateway Base URL : _____________________________________________________________ Steps 4: API Integration & External Function Info API Integration Name: __________________________________________________________ API_GCP_SERVICE_ACCOUNT: _______________________________________________________ External Function Name: ________________________________________________________ Step 5: Security Info Security Definition Name: ______________________________________________________ ```
## 2.0 Config E-UDF in GCP & Snowflake ### Step 1. Create the Cloud Function in GCP Reference Source: [Snowflake Create the Remote Service](https://docs.snowflake.com/en/sql-reference/external-functions-creating-gcp-ui-remote-service) 1. Create function from **GCP Cloud functions service** 🔍 GCP Search: Cloud Function 2. Configuration to set up the cloud function are as followed: - **Basics** - Environment: 1st gen - Function name: `` - Region: us-central1 - **Trigger** - Trigger type: HTTP - Authentication: Require authentication - **Runtime** - Memory allocated: 256MB - Timeout: 240 3. Save the configuration. 4. Proceed by clicking next and work on inputing the function program in the window. 5. Deploy the function when ready. 6. Under Google Cloud Function UI, select the deployed function and select the trigger tab. 7. Copy the trigger URL of the function and record into the template field Step 1: Cloud function trigger URL. For example: `https://us-central1-data-management-123456.cloudfunctions.net/address_to_geo_coordinate`
### Step 2. Create Proxy Service in GCP Reference Source: [Snowflake Create the Proxy Service](https://docs.snowflake.com/en/sql-reference/external-functions-creating-gcp-ui-proxy-service) 1. On your local file system (desktop), create a YAML config file with the `unique name` of your choice and end with the extension `.yml`. For example, `config_address_to_geo_coordinate.yml` 2. Copy the following configuration template into the YAML file. ```yml swagger: '2.0' info: title: API Gateway config for Snowflake external function. description: This configuration file connects the API Gateway resource to the remote service (Cloud Function). version: 1.0.0 schemes: - https produces: - application/json paths: /: post: summary: Echo the input. operationId: echo x-google-backend: address: protocol: h2 responses: '200': description: schema: type: string ``` 3. Under paths, replace `` with a unique name. For example, `func-address-to-geo`. Do not remove the forward slash. 4. Save the path name into the template field Step 2: Path Suffix. For example, `func-address-to-geo` 5. Replace `` with the value previously recorded in the template field Step 1: Cloud Function Trigger URL ***The URL should not be enclosed in "quotation marks"***. For example, `https://us-central1-data-management-123456.cloudfunctions.net/address_to_geo_coordinate` 6. Save the file and record the yaml file name into the template field Step 3: Configuration File Name. For example, `config_address_to_geo_coordinate.yml` 7. Search for Google Cloud API Gateway service in GCP. 🔍 GCP Search: API Gateway 8. Create a Gateway and configure as followed: - **API** - Select an API: Create new API - Display Name: `` - API ID: `` - Region: us-central1 - **API Config** - Select a Config: Create new API config - Upload an API Spec: Upload the config yaml file as configured in previous step from your local machine. - Display Name: `` - Select a Service Account: App Engine default service account - **Gateway details** - Display Name: `` - Location: us-central1 9. Click create gateway and wait several minutes for it to create. 10. In the API gateway page, copy the value from the managed service for the newly created API. Record this value into the template field Step 3: Managed Service Identifier. For example, `api-address-to-geo-coordinates-external-function-123456abcde.apigateway.data-management-123456.cloud.goog` 11. In the API gateway page, select the API and click on the Gateways tab. 12. Copy Gateway URL and record this information into the template file Step 3: Gateway Base URL. For example, `https://gateway-address-to-coordinates-12345abc.uc.gateway.dev` ### Step 3. Create API Integration in Snowflake Reference Source: [Snowflake Create the API Integration](https://docs.snowflake.com/en/sql-reference/external-functions-creating-gcp-common-api-integration) 1. Open a new Snowflake sql worksheet. 2. Ensure current snowflake account has accountadmin privileges. 3. Enter following sql statement template: ```sql create or replace api integration api_provider = google_api_gateway google_audience = '' api_allowed_prefixes = ('') enabled = true; ``` 4. Replace `` with `` and record the newly defined name into the template field Step 4: API Integration Name. For example, `gcp_api_integration_address_to_coordinates` 5. Replace `` with value previously recorded from template field Step 3: Managed Service Identifier 6. Execute the statement. 7. Run the following sql statement: ```sql describe integration ; ``` 8. Record `API_GCP_SERVICE_ACCOUNT` value obtained from the execution into the template field Step 4: API_GCP_SERVICE_ACCOUNT. For example, `abc1234567@gcpuscentral1-abc1.iam.gserviceaccount.com` ### Step 4. Create External Function in Snowflake Reference Source: [Snowflake Create the External Function](https://docs.snowflake.com/en/sql-reference/external-functions-creating-gcp-common-ext-function) 1. In the snowflake sql worksheet, enter the following statement: ```sql create or replace external function () returns variant api_integration = as ''; ``` 2. Replace `` with a unique function name of your choice. For example, `echo_address_to_coordinates` 3. Replace `` based on your external function configuration. For example, `id VARCHAR, full_address VARCHAR` 4. Replace `` with the value previously recorded from template field Step 4: API Integration Name 5. Replace `` with the value previously recorded from template field Step 3: Gateway Base URL and Step 2: Path Suffix. Combine the string by a forward slash ( / ) between them. For example: `'https://gateway-address-to-coordinates-12345abc.uc.gateway.dev/func-address-to-geo'` 6. Execute the statement.
## Security Policy
## 3.0 Calling E-UDF Function in Snowflake 1. Calling E-UDF in the schema by the name of the E-UDF defined previously. For example: ```sql SELECT echo_address_to_coordinates(full_address) FROM address; ``` 2. If the E-UDF is not stored in the same directory as your current working directory, you need to specify the database and schema name of the E-UDF. For example: ```sql SELECT dev_data_warehouse.dev_schema.echo_address_to_coordinates(full_address) FROM address; ```
## 4.0 Defining and calling macro and global variable for the E-UDF in DBT ### Step 1: Defining macro for geocoding address This macro converts an address string into a set of geographical coordinates. The macro will filter the rows, excluding masked data rows based on data mask pattern defined in a global variable. It accepts five input parameters representing address components and returns the parsed JSON response from the E-UDF. This set of instructions are for used in dbt. 1. Define a macro in the /macros folder directing towards the E-UDF. Remember to specify the database and schema name that store the E-UDF. The name of the macro utilized here is address_to_coordinates() Parameters: address_line_one: VARCHAR or STRING - Represents ADDRESS_LINE_ONE. address_line_two: VARCHAR or STRING - Represents ADDRESS_LINE_TWO. postcode : VARCHAR or STRING - Represents POSTCODE. district_name : VARCHAR or STRING - Represents DISTRICT_NAME. state_name : VARCHAR or STRING - Represents STATE_NAME. ```sql -- Remeber to add { } braces at the start and end of the jinja statement % -- Add double { } on the following locations -- Around the global variable var('mask_data_pattern') %- macro address_to_coordinates(address_line_one, address_line_two, postcode, district_name, state_name) -% CASE WHEN ( address_line_one IN var('mask_data_pattern') OR address_line_two in var('mask_data_pattern') OR address_line_one IS NULL AND address_line_two IS NULL ) THEN NULL ELSE PARSE_JSON(global_function.eudf.echo_address_to_coordinates(address_line_one, address_line_two, postcode, district_name, state_name)) END %- endmacro -% ``` ### Step 2: Defining global variable A global variable can be defined in `dbt_project.yml` file. The variable here is called `mask_data_pattern` which is to store a list of string patterns used for masking data. ```yml vars: mask_data_pattern: ('*MASK*', '****') ``` ### Step 3: Implement incremental model Include a materialize configuration block to the model to specify the model as 'incremental' and input the unique_key value(s). - Snowflake by default uses the merge method for incremental strategy. Thus this model adopts the default method. - The unique_key must not have null and duplicates. It is suggested to use the primary key of the table instead. - One or more unique_key can be selected. - Add double { } to wrap around the configuration block ```sql config( materialized='incremental', unique_key=['COMPANY_ID', 'COMPANY_ADDRESS_ID'] ) ``` Inlcude a filter block using is_incremental() function for the model to define how to update the model. The following example uses the 'UPDATED_DATETIME' as the filter to identify new rows to undergo transformation. - Add double { } to wrap around the following locations: - ref('test_address__mock_data') - this - Remeber to add { } braces at the start and end of the jinja statement % ```sql WITH events as ( SELECT * FROM ref('test_address__mock_data') % if is_incremental() % WHERE UPDATED_DATETIME > (SELECT max(UPDATED_DATETIME) FROM this ) %- endif % ), ``` ### Step 4: Calling the macro To call the defined macro, the following syntax can be utilized, take note to **include** the **double curly braces**: - In addition, underlying values can be derived from the results of the macro. You can access the values in the list by referring to the name of the key. - Shown in the second code block for example, the latitude and longitude of the location is retrieved and stored in a new column. ```sql -- Add double { } on the following locations -- Around the E-UDF address_to_coordinates() -- Around the ref('test_address__mock_data') WITH geocoded_address AS ( SELECT *, address_to_coordinates( 'ADDRESS_LINE_ONE', 'ADDRESS_LINE_TWO', 'POSTCODE', 'DISTRICT_NAME', 'STATE_NAME' ) AS FUNCTION_JSON_RETURN FROM events ), json_data_extraction AS ( SELECT FUNCTION_JSON_RETURN:result:geometry:location:lat::float AS LATITUDE, FUNCTION_JSON_RETURN:result:geometry:location:lng::float AS LONGITUDE, FUNCTION_JSON_RETURN:geocoding_api_returns AS API_GEOCODING_JSON_RETURN, FUNCTION_JSON_RETURN:result AS API_PLACE_DETAILS_JSON_RETURN, * FROM geocoded_address ) ``` ## 5.0 Local Variables Several local variables are defined using Jinja templating: - `days_of_week`: A list of strings representing the days of the week. - `masked_address_sql`: A SQL condition statement to check if the address line is masked. This variable also refers to the global variable 'mask_data_pattern'. - `dummy_value`: A dummy value used for placeholders. - `dummy_long_string`: A long dummy string used for placeholders. - `dummy_short_string`: A short dummy string used for placeholders. - `dummy_phone`: A dummy phone number string used for placeholders. - `dummy_operating_hours`: A dummy operating hours string used for placeholders. - `dummy_website`: A dummy website URL string used for placeholders. ## 6.0 CTEs (Common Table Expressions) `company_addresses_with_row_num`
This CTE assigns a row number to each row in the company_addresses table and filters the data based on the incremental update condition if the job is incremental. `company_addressess_join_companies_countries_states_districts`
This CTE joins the company_addresses_with_row_num CTE with the countries, states, and districts tables to form a fuller table. `batched_data_batch`
This CTE splits the data rows into batches for processing. Each batch contains up to 10 rows. This is to avoid timeout and resource exhaustion experienced in snowflake caused by concurrency. `address_enrich_using_google_api_batch`
This CTE enriches the address data by using the address_to_coordinates E-UDF, which calls the Google Maps API to obtain geocoding and location information for each address in the batch. `union_all_batch_address_enriched`
This CTE combines the results from all the batches into a single table. `operation_hours_json`
This CTE extracts the operating hour range for each day of the week from the JSON data returned by the API. `operation_hour_range_extraction`
This CTE further processes the extracted operating hour ranges and removes the day prefixes. `operation_hour_to_24_hour_transform`
This CTE converts the operating hour ranges to a 24-hour format. `special_single_operation_hour_to_24_hour_transform`
This CTE handles cases where there are only single operating hours within a day but do not have an AM/PM indicator and converts them to a 24-hour format. `special_double_operation_hour_to_24_hour_transform`
This CTE handles cases where there are two operating hours within a day and converts them to a 24-hour format. `concatenate_operation_hour`
This CTE concatenates the first and second transformed time ranges for each day of the week. For scenarios with only single operating hours within a day, the original time range will be returned. `google_place_api_details_extraction`
This CTE further extracts potentially valuable information returned by the API and flatten them. `masked_data_transformation`
This CTE applies transformations to the selected metrics and dimensions. It replaces sensitive information with dummy values if the address is masked. {% enddocs %}