| defmodule Plausible.DataMigration.LocationsSync do |
| @moduledoc """ |
| ClickHouse locations data migration for storing location names in ClickHouse. |
| |
| Only run when `Location.version()` changes: either as a migration or in cron. |
| |
| The migration: |
| 1. Truncates existing `location_data` table (if exists) |
| 2. Creates new table (if needed) |
| 3. Inserts new data from Location module |
| 4. (Re-)Creates dictionary to read location data from table |
| 5. Creates ALIAS columns in `events_v2`, `sessions_v2` and `imported_locations` table to make reading location names easy |
| 6. Updates table comment for `location_data` to indicate last version synced. |
| |
| Note that the dictionary is large enough to cache the whole dataset in memory, making lookups fast. |
| |
| This migration is intended to be idempotent and rerunnable - if run multiple times, it should always set things to the same |
| result as if run once. |
| |
| SQL files available at: priv/data_migrations/LocationsSync/sql |
| """ |
| alias Plausible.ClickhouseLocationData |
|
|
| use Plausible.DataMigration, dir: "LocationsSync", repo: Plausible.IngestRepo |
|
|
| @columns [ |
| %{ |
| table: "events_v2", |
| column_name: "country_name", |
| type: "country", |
| input_column: "country_code" |
| }, |
| %{ |
| table: "events_v2", |
| column_name: "region_name", |
| type: "subdivision", |
| input_column: "subdivision1_code" |
| }, |
| %{ |
| table: "events_v2", |
| column_name: "city_name", |
| type: "city", |
| input_column: "city_geoname_id" |
| }, |
| %{ |
| table: "sessions_v2", |
| column_name: "country_name", |
| type: "country", |
| input_column: "country_code" |
| }, |
| %{ |
| table: "sessions_v2", |
| column_name: "region_name", |
| type: "subdivision", |
| input_column: "subdivision1_code" |
| }, |
| %{ |
| table: "sessions_v2", |
| column_name: "city_name", |
| type: "city", |
| input_column: "city_geoname_id" |
| }, |
| %{ |
| table: "imported_locations", |
| column_name: "country_name", |
| type: "country", |
| input_column: "country" |
| }, |
| %{ |
| table: "imported_locations", |
| column_name: "region_name", |
| type: "subdivision", |
| input_column: "region" |
| }, |
| %{ |
| table: "imported_locations", |
| column_name: "city_name", |
| type: "city", |
| input_column: "city" |
| } |
| ] |
|
|
| def out_of_date?() do |
| case run_sql("get-location-data-table-comment") do |
| {:ok, %{rows: [[stored_version]]}} -> stored_version != Location.version() |
| _ -> true |
| end |
| end |
|
|
| def run() do |
| cluster? = Plausible.IngestRepo.clustered_table?("sessions_v2") |
|
|
| {:ok, _} = run_sql("truncate-location-data-table", cluster?: cluster?) |
|
|
| {:ok, _} = |
| run_sql("create-location-data-table", |
| cluster?: cluster?, |
| table_settings: Plausible.MigrationUtils.table_settings_expr(:suffix) |
| ) |
|
|
| countries = |
| Location.Country.all() |
| |> Enum.map(fn %Location.Country{alpha_2: alpha_2, name: name} -> |
| %{type: "country", id: alpha_2, name: name} |
| end) |
|
|
| subdivisions = |
| Location.Subdivision.all() |
| |> Enum.map(fn %Location.Subdivision{code: code, name: name} -> |
| %{type: "subdivision", id: code, name: name} |
| end) |
|
|
| cities = |
| Location.City.all() |
| |> Enum.map(fn %Location.City{id: id, name: name} -> |
| %{type: "city", id: Integer.to_string(id), name: name} |
| end) |
|
|
| insert_data = Enum.concat([countries, subdivisions, cities]) |
| @repo.insert_all(ClickhouseLocationData, insert_data) |
|
|
| {:ok, _} = |
| run_sql("update-location-data-dictionary", |
| cluster?: cluster?, |
| dictionary_connection_params: Plausible.MigrationUtils.dictionary_connection_params() |
| ) |
|
|
| for column <- @columns do |
| {:ok, _} = |
| run_sql("add-alias-column", |
| cluster?: cluster?, |
| table: column.table, |
| column_name: column.column_name, |
| type: column.type, |
| input_column: column.input_column |
| ) |
| end |
|
|
| {:ok, _} = |
| run_sql("update-location-data-table-comment", |
| cluster?: cluster?, |
| version: Location.version() |
| ) |
| end |
| end |
|
|