├── .gitignore ├── README.md └── citus ├── AdsOfACompany.graphql ├── AllCampaignsOfCompanyWithDetails.graphql ├── AllClicksAndImpressionsOfCompany.graphql ├── CampaignRanksOfCompany.graphql ├── CampaignRanksOfCompanyWithRelationship.graphql ├── ErrAdsOfACompany.graphql ├── ErrCampaignRanksOfCompany.graphql ├── README.md ├── docker-compose.yaml ├── load_sample_data.sh ├── metadata.json ├── metadata.yaml └── metadata_with_view.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hasura GraphQL engine on various Postgres flavours 2 | 3 | ## TimescaleDB 4 | 5 | 1. Follow the timescale tutorial to install and run a timescale db instance. 6 | (https://docs.timescale.com/v0.9/getting-started/installation/mac/installation-homebrew) 7 | 8 | 2. Follow this tutorial to import a sample dataset. Here we are using New York City taxicab data. (https://docs.timescale.com/v0.9/tutorials/tutorial-hello-nyc). 9 | Follow this tutorial till the point to import data. 10 | 11 | 3. Follow https://docs.hasura.io to run the GraphQL engine - with the 12 | correct database credentials pointing to your timescaledb instance. 13 | 14 | 4. Open the console `hasura console`, and track all the tables. 15 | 16 | 5. Create views with timescaledb specific functions, for timescale specific queries. 17 | 18 | 6. Use Run SQL in console to create these views and track them: 19 | 20 | ```sql 21 | -- Average fare amount of rides with 2+ passengers by day 22 | CREATE VIEW avg_fare_w_2_plus_passenger_by_day AS ( 23 | SELECT date_trunc('day', pickup_datetime) as day, avg(fare_amount) as avg_fare 24 | FROM rides 25 | WHERE passenger_count > 1 26 | GROUP BY day ORDER BY day 27 | ); 28 | 29 | -- Total number of rides by day for first 5 days 30 | CREATE VIEW ride_count_by_day AS ( 31 | SELECT date_trunc('day', pickup_datetime) as day, COUNT(*) FROM rides 32 | GROUP BY day ORDER BY day 33 | ); 34 | 35 | -- Number of rides by 5 minute intervals 36 | -- (using the TimescaleDB "time_bucket" function) 37 | CREATE VIEW rides_in_5min_intervals AS ( 38 | SELECT time_bucket('5 minute', pickup_datetime) AS five_min_interval, count(*) as rides 39 | FROM rides 40 | GROUP BY five_min_interval ORDER BY five_min_interval 41 | ); 42 | ``` 43 | 44 | 7. Once the above is done, then we can run GraphQL queries on these views. 45 | 46 | ```graphql 47 | query { 48 | avg_fare_w_2_plus_passenger_by_day(limit: 10) { 49 | day 50 | avg_fare 51 | } 52 | } 53 | 54 | query FirstTenDaysWithRidesMoreThan30k { 55 | ride_count_by_day(limit: 10, where: {count : {_gt: 30000}}) { 56 | day 57 | count 58 | } 59 | } 60 | 61 | query NoOfRidesIn5minIntervalBefore02Jan { 62 | rides_in_5min_intervals(where: {five_min_interval:{_lt: "2016-01-02 00:00"}}) { 63 | five_min_interval 64 | rides 65 | } 66 | } 67 | ``` 68 | 69 | ## Citus DB 70 | 71 | See details in `citus/README.md`. 72 | -------------------------------------------------------------------------------- /citus/AdsOfACompany.graphql: -------------------------------------------------------------------------------- 1 | query ($companyId: bigint!) { 2 | ads (where: {company_id: {_eq: $companyId}}) { 3 | name 4 | image_url 5 | campaign { 6 | name 7 | company { 8 | name 9 | } 10 | } 11 | impressions (where: {company_id: {_eq: $companyId}}) { 12 | cost_per_impression_usd 13 | seen_at 14 | site_url 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /citus/AllCampaignsOfCompanyWithDetails.graphql: -------------------------------------------------------------------------------- 1 | query AllCampaignsOfCompany5 ($companyId: bigint!) { 2 | campaigns(where: {company_id: {_eq: $companyId}}) { 3 | name 4 | company { 5 | name 6 | } 7 | cost_model 8 | monthly_budget 9 | state 10 | ads (where: {company_id: {_eq: $companyId}}){ 11 | id 12 | campaign { 13 | name 14 | } 15 | clicks (where: {company_id: {_eq: $companyId}}) { 16 | clicked_at 17 | site_url 18 | cost_per_click_usd 19 | } 20 | impressions (where: {company_id: {_eq: $companyId}}){ 21 | seen_at 22 | site_url 23 | cost_per_impression_usd 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /citus/AllClicksAndImpressionsOfCompany.graphql: -------------------------------------------------------------------------------- 1 | query AllClicksAndImpressionsOfCompany ($companyId: bigint!) { 2 | clicks (where: {company_id: {_eq: $companyId}}){ 3 | cost_per_click_usd 4 | clicked_at 5 | site_url 6 | user_ip 7 | user_data 8 | ad { 9 | name 10 | } 11 | } 12 | impressions (where: {company_id: {_eq: $companyId}}) { 13 | cost_per_impression_usd 14 | seen_at 15 | site_url 16 | user_ip 17 | user_data 18 | ad { 19 | name 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /citus/CampaignRanksOfCompany.graphql: -------------------------------------------------------------------------------- 1 | query CampaignRanksOfCompany($companyId: bigint!) { 2 | campaign_ranks (where: {company_id: {_eq: $companyId}}) { 3 | campaign_id 4 | rank 5 | n_impressions 6 | ad_id 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /citus/CampaignRanksOfCompanyWithRelationship.graphql: -------------------------------------------------------------------------------- 1 | query CampaignRanksOfCompany($companyId: bigint!) { 2 | campaign_ranks ( 3 | where: { 4 | _and: [ 5 | {company_id: {_eq: $companyId}}, 6 | {ad: {company_id: {_eq: $companyId}}} 7 | ] 8 | } 9 | ) { 10 | campaign_id 11 | rank 12 | n_impressions 13 | ad_id 14 | ad { 15 | id 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /citus/ErrAdsOfACompany.graphql: -------------------------------------------------------------------------------- 1 | query ($companyId: bigint!) { 2 | ads (where: {company_id: {_eq: $companyId}}) { 3 | name 4 | image_url 5 | campaign { 6 | name 7 | company { 8 | name 9 | } 10 | } 11 | impressions (where: {company_id: {_eq: $companyId}}) { 12 | cost_per_impression_usd 13 | seen_at 14 | site_url 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /citus/ErrCampaignRanksOfCompany.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | campaign_ranks (where: {company_id: {_eq: 5}}) { 3 | campaign { 4 | name 5 | } 6 | rank 7 | n_impressions 8 | ad { 9 | name 10 | } 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /citus/README.md: -------------------------------------------------------------------------------- 1 | # Citus DB 2 | 3 | Hasura on Citus. This uses Citus 8.0 and Hasura v1.0.0-alpha31. 4 | 5 | ## Setup 6 | 7 | ### Pre-requisites 8 | 9 | 1. docker-compose 10 | 2. psql 11 | 12 | ### Run citus and hasura 13 | 14 | 1. Download the `docker-compose.yaml` file 15 | 16 | 2. Run `docker-compose -p citus up -d` 17 | 18 | This will start a Citus cluster with one worker, and a Hasura GraphQL Engine pointing to the citus instance. 19 | 20 | ### Import sample data from Citus multi-tenant example app dataset 21 | 22 | 1. Download the `load_sample_data.sh` and run it to load sample data into the citus instance. 23 | 24 | ```shell 25 | $ chmod +x load_sample_data.sh && ./load_sample_data.sh 26 | ``` 27 | 28 | ### Add hasura metadata 29 | 30 | 1. Open the console 31 | 2. Track all the tables 32 | 3. Create relationships mentioned below, or use the `metadata.json` file to import the relationships. 33 | 34 | 35 | ## The schema 36 | 37 | All the tables are distributed tables. The distribution column is `company_id` 38 | (for `companies` table it is the `id` column). 39 | 40 | Following are the tables and foreign key references among them: 41 | 42 | 1. `companies` 43 | 2. `campaigns` : `campaigns.company_id` -> `companies.id` 44 | 3. `ads` : `ads.campaign_id` -> `campaigns.id` , `ads.company_id` -> `companies.id` 45 | 4. `clicks` : `clicks.ad_id` -> `ads.id` , `clicks.company_id` -> `companies.id` 46 | 5. `impressions` : `impressions.ad_id` -> `ads.id` , `impressions.company_id` -> `companies.id` 47 | 48 | ## Hasura relationships 49 | 50 | 1. `campaigns` has an object relationship to `companies` and array relationships to `ads` 51 | 2. `ads` has an object relationship to `campaigns` and array relationships to `clicks` and `impressions` 52 | 3. `clicks` has an object relationship to `ads` 53 | 4. `impressions` has an object relationship to `ads` 54 | 55 | 56 | 57 | ## Querying the existing data 58 | 59 | **NOTE**: `company_id` is required at top-level for all queries, because its the distribution column in citus. 60 | 61 | ```graphql 62 | query AllCampaignsOfCompany ($companyId: bigint!) { 63 | campaigns(where: {company_id: {_eq: $companyId}}) { 64 | name 65 | company { 66 | name 67 | } 68 | cost_model 69 | monthly_budget 70 | state 71 | } 72 | } 73 | 74 | query AllCampaignsOfCompanyWithDetails ($companyId: bigint!) { 75 | campaigns(where: {company_id: {_eq: $companyId}}) { 76 | name 77 | company { 78 | name 79 | } 80 | cost_model 81 | monthly_budget 82 | state 83 | ads (where: {company_id: {_eq: $companyId}}) { 84 | id 85 | campaign { 86 | name 87 | } 88 | clicks (where: {company_id: {_eq: $companyId}}) { 89 | clicked_at 90 | site_url 91 | cost_per_click_usd 92 | } 93 | impressions (where: {company_id: {_eq: $companyId}}) { 94 | seen_at 95 | site_url 96 | cost_per_impression_usd 97 | } 98 | } 99 | } 100 | } 101 | 102 | query AllClicksAndImpressionsOfCompany ($companyId: bigint!) { 103 | clicks (where: {company_id: {_eq: $companyId}}){ 104 | cost_per_click_usd 105 | clicked_at 106 | site_url 107 | user_ip 108 | user_data 109 | ad { 110 | name 111 | } 112 | } 113 | impressions (where: {company_id: {_eq: $companyId}}) { 114 | cost_per_impression_usd 115 | seen_at 116 | site_url 117 | user_ip 118 | user_data 119 | ad { 120 | name 121 | } 122 | } 123 | } 124 | 125 | ``` 126 | 127 | ### Additional querying with Citus specific functions 128 | 129 | Create views with complex SQL. 130 | 131 | This is the same SQL as the last portion of this (section in Citus 132 | docs)[https://docs.citusdata.com/en/v7.4/use_cases/multi_tenant.html#integrating-applications], 133 | with some minor modifications to aggregate all companies. 134 | 135 | ```sql 136 | CREATE OR REPLACE VIEW "campaign_ranks" AS 137 | SELECT a.campaign_id, 138 | rank() OVER (PARTITION BY a.campaign_id, a.company_id ORDER BY a.campaign_id, (count(*)) DESC) AS rank, 139 | count(*) AS n_impressions, 140 | a.id AS ad_id, 141 | a.company_id 142 | FROM ads a, 143 | impressions i 144 | WHERE ((i.company_id = a.company_id) AND (i.ad_id = a.id)) 145 | GROUP BY a.campaign_id, a.id, a.company_id 146 | ORDER BY a.campaign_id, (count(*)) DESC; 147 | ``` 148 | 149 | Now we can query this view in our GraphQL query as well: 150 | 151 | ```graphql 152 | query { 153 | campaign_ranks (where: {company_id: {_eq: 5}}) { 154 | campaign_id 155 | rank 156 | n_impressions 157 | ad_id 158 | } 159 | } 160 | ``` 161 | 162 | ## Errors/Failures 163 | 164 | 1. Following query does not work: 165 | 166 | ```graphql 167 | query AdsOfACompany ($companyId: bigint!) { 168 | ads (where: {company_id: {_eq: $companyId}}) { 169 | name 170 | image_url 171 | campaign { 172 | name 173 | company { 174 | name 175 | } 176 | } 177 | impressions (where: {company_id: {_eq: $companyId}}) { 178 | cost_per_impression_usd 179 | seen_at 180 | site_url 181 | } 182 | } 183 | } 184 | ``` 185 | 186 | Results in error: 187 | 188 | ```json 189 | { 190 | "errors": [ 191 | { 192 | "internal": { 193 | "statement": "SELECT coalesce(json_agg(\"root\" ), '[]' ) AS \"root\" FROM (SELECT row_to_json((SELECT \"_11_e\" FROM (SELECT \"_0_root.base\".\"name\" AS \"name\", \"_0_root.base\".\"image_url\" AS \"image_url\", \"_6_root.or.campaign\".\"campaign\" AS \"campaign\", \"_10_root.ar.root.impressions\".\"impressions\" AS \"impressions\" ) AS \"_11_e\" ) ) AS \"root\" FROM (SELECT * FROM \"public\".\"ads\" WHERE (((\"public\".\"ads\".\"company_id\") = ($1)) OR (((\"public\".\"ads\".\"company_id\") IS NULL) AND (($1) IS NULL))) ) AS \"_0_root.base\" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT \"_5_e\" FROM (SELECT \"_1_root.or.campaign.base\".\"name\" AS \"name\", \"_4_root.or.campaign.or.company\".\"company\" AS \"company\" ) AS \"_5_e\" ) ) AS \"campaign\" FROM (SELECT * FROM \"public\".\"campaigns\" WHERE (((\"_0_root.base\".\"campaign_id\") = (\"id\")) AND ((\"_0_root.base\".\"company_id\") = (\"company_id\"))) ) AS \"_1_root.or.campaign.base\" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT \"_3_e\" FROM (SELECT \"_2_root.or.campaign.or.company.base\".\"name\" AS \"name\" ) AS \"_3_e\" ) ) AS \"company\" FROM (SELECT * FROM \"public\".\"companies\" WHERE ((\"_1_root.or.campaign.base\".\"company_id\") = (\"id\")) ) AS \"_2_root.or.campaign.or.company.base\" ) AS \"_4_root.or.campaign.or.company\" ON ('true') ) AS \"_6_root.or.campaign\" ON ('true') LEFT OUTER JOIN LATERAL (SELECT coalesce(json_agg(\"impressions\" ), '[]' ) AS \"impressions\" FROM (SELECT row_to_json((SELECT \"_8_e\" FROM (SELECT \"_7_root.ar.root.impressions.base\".\"cost_per_impression_usd\" AS \"cost_per_impression_usd\", \"_7_root.ar.root.impressions.base\".\"seen_at\" AS \"seen_at\", \"_7_root.ar.root.impressions.base\".\"site_url\" AS \"site_url\" ) AS \"_8_e\" ) ) AS \"impressions\" FROM (SELECT * FROM \"public\".\"impressions\" WHERE ((((\"_0_root.base\".\"company_id\") = (\"company_id\")) AND ((\"_0_root.base\".\"id\") = (\"ad_id\"))) AND (((\"public\".\"impressions\".\"company_id\") = ($2)) OR (((\"public\".\"impressions\".\"company_id\") IS NULL) AND (($2) IS NULL)))) ) AS \"_7_root.ar.root.impressions.base\" ) AS \"_9_root.ar.root.impressions\" ) AS \"_10_root.ar.root.impressions\" ON ('true') ) AS \"_12_root\" ", 194 | "prepared": true, 195 | "error": { 196 | "exec_status": "FatalError", 197 | "hint": null, 198 | "message": "cannot push down this subquery", 199 | "status_code": "0A000", 200 | "description": "Aggregates without group by are currently unsupported when a subquery references a column from another query" 201 | }, 202 | "arguments": [ 203 | "(Oid 20,Just (\"\\NUL\\NUL\\NUL\\NUL\\NUL\\NUL\\NUL\\ENQ\",Binary))", 204 | "(Oid 20,Just (\"\\NUL\\NUL\\NUL\\NUL\\NUL\\NUL\\NUL\\ENQ\",Binary))" 205 | ] 206 | }, 207 | "path": "$", 208 | "error": "postgres query error", 209 | "code": "postgres-error" 210 | } 211 | ] 212 | } 213 | ``` 214 | 215 | 2. Add object relationships in the `campaign_ranks` view, to the `campaigns` table and `ads` table. 216 | 217 | ```json 218 | [ 219 | { 220 | "using": { 221 | "manual_configuration": { 222 | "remote_table": "companies", 223 | "column_mapping": { 224 | "company_id": "id" 225 | } 226 | } 227 | }, 228 | "name": "company", 229 | "comment": null 230 | }, 231 | { 232 | "using": { 233 | "manual_configuration": { 234 | "remote_table": "ads", 235 | "column_mapping": { 236 | "ad_id": "id" 237 | } 238 | } 239 | }, 240 | "name": "ad", 241 | "comment": null 242 | }, 243 | { 244 | "using": { 245 | "manual_configuration": { 246 | "remote_table": "campaigns", 247 | "column_mapping": { 248 | "ad_id": "id" 249 | } 250 | } 251 | }, 252 | "name": "campaign", 253 | "comment": null 254 | } 255 | ] 256 | ``` 257 | 258 | Then make this query: 259 | 260 | ```graphql 261 | query { 262 | campaign_ranks (where: {company_id: {_eq: 5}}) { 263 | campaign { 264 | name 265 | } 266 | rank 267 | n_impressions 268 | ad { 269 | name 270 | } 271 | } 272 | } 273 | ``` 274 | 275 | Fails with error: 276 | 277 | ```json 278 | { 279 | "errors": [ 280 | { 281 | "internal": { 282 | "statement": "SELECT coalesce(json_agg(\"root\" ), '[]' ) AS \"root\" FROM (SELECT row_to_json((SELECT \"_7_e\" FROM (SELECT \"_6_root.or.campaign\".\"campaign\" AS \"campaign\", (\"_0_root.base\".\"rank\")::text AS \"rank\", (\"_0_root.base\".\"n_impressions\")::text AS \"n_impressions\", \"_3_root.or.ad\".\"ad\" AS \"ad\" ) AS \"_7_e\" ) ) AS \"root\" FROM (SELECT * FROM \"public\".\"campaign_ranks\" WHERE (((\"public\".\"campaign_ranks\".\"company_id\") = ($1)) OR (((\"public\".\"campaign_ranks\".\"company_id\") IS NULL) AND (($1) IS NULL))) ) AS \"_0_root.base\" LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT \"_2_e\" FROM (SELECT \"_1_root.or.ad.base\".\"name\" AS \"name\" ) AS \"_2_e\" ) ) AS \"ad\" FROM (SELECT * FROM \"public\".\"ads\" WHERE ((\"_0_root.base\".\"ad_id\") = (\"id\")) ) AS \"_1_root.or.ad.base\" ) AS \"_3_root.or.ad\" ON ('true') LEFT OUTER JOIN LATERAL (SELECT row_to_json((SELECT \"_5_e\" FROM (SELECT \"_4_root.or.campaign.base\".\"name\" AS \"name\" ) AS \"_5_e\" ) ) AS \"campaign\" FROM (SELECT * FROM \"public\".\"campaigns\" WHERE ((\"_0_root.base\".\"campaign_id\") = (\"id\")) ) AS \"_4_root.or.campaign.base\" ) AS \"_6_root.or.campaign\" ON ('true') ) AS \"_8_root\" ", 283 | "prepared": true, 284 | "error": { 285 | "exec_status": "FatalError", 286 | "hint": "Consider using an equality filter on the distributed table's partition column.", 287 | "message": "could not run distributed query with subquery outside the FROM and WHERE clauses", 288 | "status_code": "0A000", 289 | "description": null 290 | }, 291 | "arguments": [ 292 | "(Oid 20,Just (\"\\NUL\\NUL\\NUL\\NUL\\NUL\\NUL\\NUL\\ENQ\",Binary))" 293 | ] 294 | }, 295 | "path": "$", 296 | "error": "postgres query error", 297 | "code": "postgres-error" 298 | } 299 | ] 300 | } 301 | ``` 302 | 303 | 304 | 3. Add permissions to the `companies` table: 305 | 306 | Role: user 307 | Query: select 308 | Columns: all 309 | Custom check: `{"id":{"_eq":"x-hasura-company-id"}}` 310 | 311 | Then making query: 312 | 313 | ```graphql 314 | query { 315 | companies (where: {id: {_eq: 5}}) { 316 | name 317 | } 318 | } 319 | ``` 320 | 321 | With headers: 322 | 323 | ``` 324 | x-hasura-role: user 325 | x-hasura-company-id: 5 326 | ``` 327 | 328 | Results in: 329 | 330 | ```json 331 | { 332 | "errors": [ 333 | { 334 | "path": "$", 335 | "error": "postgres query error", 336 | "code": "postgres-error" 337 | } 338 | ] 339 | } 340 | ``` 341 | 342 | Docker logs output: 343 | ``` 344 | WARNING: unrecognized configuration parameter "hasura.user" 345 | CONTEXT: while executing command on citus_worker_1:5432 346 | {"timestamp":"2018-12-17T13:52:30.729+0000","level":"info","type":"http-log","detail":{"status":500,"query_hash":"166c2438e839e201bd91a57873629136a9ec1d92","http_version":"HTTP/1.1","query_execution_time":1.7499646e-2,"request_id":null,"url":"/v1alpha1/graphql","ip":"122.171.161.60","response_size":1006,"user":{"x-hasura-role":"user","x-hasura-company-id":"5"},"method":"POST","detail":{"error":{"internal":{"statement":"SELECT coalesce(json_agg(\"root\" ), '[]' ) AS \"root\" FROM (SELECT row_to_json((SELECT \"_1_e\" FROM (SELECT \"_0_root.base\".\"name\" AS \"name\" ) AS \"_1_e\" ) ) AS \"root\" FROM (SELECT * FROM \"public\".\"companies\" WHERE ((((\"public\".\"companies\".\"id\") = (((current_setting('hasura.user')::json->>'x-hasura-company-id'))::bigint)) OR (((\"public\".\"companies\".\"id\") IS NULL) AND ((((current_setting('hasura.user')::json->>'x-hasura-company-id'))::bigint) IS NULL))) AND (((\"public\".\"companies\".\"id\") = ($1)) OR (((\"public\".\"companies\".\"id\") IS NULL) AND (($1) IS NULL)))) ) AS \"_0_root.base\" ) AS \"_2_root\" ","prepared":true,"error":{"exec_status":"FatalError","hint":null,"message":"could not receive query results","status_code":"XX000","description":null},"arguments":["(Oid 20,Just (\"\\NUL\\NUL\\NUL\\NUL\\NUL\\NUL\\NUL\\ENQ\",Binary))"]},"path":"$","error":"postgres query error","code":"postgres-error"},"request":"{\"query\":\"query {\\n companies (where: {id: {_eq: 5}}) {\\n name\\n }\\n}\",\"variables\":null}"}}} 347 | 348 | ``` 349 | -------------------------------------------------------------------------------- /citus/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | services: 4 | master: 5 | container_name: "${COMPOSE_PROJECT_NAME:-citus}_master" 6 | image: 'citusdata/citus:8.0.0' 7 | ports: 8 | - "${MASTER_EXTERNAL_PORT:-5432}:5432" 9 | labels: 10 | - 'com.citusdata.role=Master' 11 | worker: 12 | image: 'citusdata/citus:8.0.0' 13 | labels: 14 | - 'com.citusdata.role=Worker' 15 | depends_on: 16 | manager: 17 | condition: service_healthy 18 | manager: 19 | container_name: "${COMPOSE_PROJECT_NAME:-citus}_manager" 20 | image: 'citusdata/membership-manager:0.2.0' 21 | volumes: 22 | - '/var/run/docker.sock:/var/run/docker.sock' 23 | depends_on: 24 | master: 25 | condition: service_healthy 26 | 27 | hasura-graphql-engine: 28 | container_name: "${COMPOSE_PROJECT_NAME:-citus}_hasura" 29 | image: 'hasura/graphql-engine:v1.0.0-alpha31' 30 | ports: 31 | - "${HASURA_EXTERNAL_PORT:-8080}:8080" 32 | depends_on: 33 | - master 34 | restart: always 35 | environment: 36 | HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:@master:5432/postgres 37 | HASURA_GRAPHQL_ENABLE_CONSOLE: "true" # set to "false" to disable console 38 | -------------------------------------------------------------------------------- /citus/load_sample_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # load sample distributed table data for citus 6 | # https://docs.citusdata.com/en/v8.0/use_cases/multi_tenant.html#let-s-make-an-app-ad-analytics 7 | 8 | # download and import the sample schema from https://examples.citusdata.com/mt_ref_arch/schema.sql 9 | 10 | curl -o sample_schema.sql https://examples.citusdata.com/mt_ref_arch/schema.sql 11 | 12 | cat <> sample_schema.sql 13 | 14 | CREATE EXTENSION IF NOT EXISTS citus; 15 | 16 | SELECT create_distributed_table('companies', 'id'); 17 | SELECT create_distributed_table('campaigns', 'company_id'); 18 | SELECT create_distributed_table('ads', 'company_id'); 19 | SELECT create_distributed_table('clicks', 'company_id'); 20 | SELECT create_distributed_table('impressions', 'company_id'); 21 | EOF 22 | 23 | psql -U postgres -h localhost -d postgres < sample_schema.sql 24 | 25 | # download and ingest datasets from the shell 26 | 27 | for dataset in companies campaigns ads clicks impressions geo_ips; do 28 | curl -O https://examples.citusdata.com/mt_ref_arch/${dataset}.csv 29 | done 30 | 31 | cat < load_sample_data.sql 32 | \copy companies from 'companies.csv' with csv 33 | \copy campaigns from 'campaigns.csv' with csv 34 | \copy ads from 'ads.csv' with csv 35 | \copy clicks from 'clicks.csv' with csv 36 | \copy impressions from 'impressions.csv' with csv 37 | EOF 38 | 39 | psql -U postgres -h localhost -d postgres < load_sample_data.sql 40 | -------------------------------------------------------------------------------- /citus/metadata.json: -------------------------------------------------------------------------------- 1 | {"remote_schemas":[],"tables":[{"table":"clicks","object_relationships":[{"using":{"manual_configuration":{"remote_table":"ads","column_mapping":{"company_id":"company_id","ad_id":"id"}}},"name":"ad","comment":null}],"array_relationships":[],"insert_permissions":[],"select_permissions":[],"update_permissions":[],"delete_permissions":[],"event_triggers":[]},{"table":"campaigns","object_relationships":[{"using":{"foreign_key_constraint_on":"company_id"},"name":"company","comment":null}],"array_relationships":[{"using":{"manual_configuration":{"remote_table":"ads","column_mapping":{"company_id":"company_id","id":"campaign_id"}}},"name":"ads","comment":null}],"insert_permissions":[],"select_permissions":[],"update_permissions":[],"delete_permissions":[],"event_triggers":[]},{"table":"ads","object_relationships":[{"using":{"manual_configuration":{"remote_table":"campaigns","column_mapping":{"company_id":"company_id","campaign_id":"id"}}},"name":"campaign","comment":null}],"array_relationships":[{"using":{"manual_configuration":{"remote_table":"clicks","column_mapping":{"company_id":"company_id","id":"ad_id"}}},"name":"clicks","comment":null},{"using":{"manual_configuration":{"remote_table":"impressions","column_mapping":{"company_id":"company_id","id":"ad_id"}}},"name":"impressions","comment":null}],"insert_permissions":[],"select_permissions":[],"update_permissions":[],"delete_permissions":[],"event_triggers":[]},{"table":"companies","object_relationships":[],"array_relationships":[{"using":{"foreign_key_constraint_on":{"column":"company_id","table":"campaigns"}},"name":"campaigns","comment":null}],"insert_permissions":[],"select_permissions":[],"update_permissions":[],"delete_permissions":[],"event_triggers":[]},{"table":"impressions","object_relationships":[{"using":{"manual_configuration":{"remote_table":"ads","column_mapping":{"company_id":"company_id","ad_id":"id"}}},"name":"ad","comment":null}],"array_relationships":[],"insert_permissions":[],"select_permissions":[],"update_permissions":[],"delete_permissions":[],"event_triggers":[]}],"query_templates":[]} -------------------------------------------------------------------------------- /citus/metadata.yaml: -------------------------------------------------------------------------------- 1 | query_templates: [] 2 | remote_schemas: [] 3 | tables: 4 | - select_permissions: [] 5 | object_relationships: 6 | - using: 7 | manual_configuration: 8 | remote_table: ads 9 | column_mapping: 10 | company_id: company_id 11 | ad_id: id 12 | name: ad 13 | comment: null 14 | event_triggers: [] 15 | insert_permissions: [] 16 | table: clicks 17 | update_permissions: [] 18 | delete_permissions: [] 19 | array_relationships: [] 20 | - select_permissions: [] 21 | object_relationships: 22 | - using: 23 | foreign_key_constraint_on: company_id 24 | name: company 25 | comment: null 26 | event_triggers: [] 27 | insert_permissions: [] 28 | table: campaigns 29 | update_permissions: [] 30 | delete_permissions: [] 31 | array_relationships: 32 | - using: 33 | manual_configuration: 34 | remote_table: ads 35 | column_mapping: 36 | company_id: company_id 37 | id: campaign_id 38 | name: ads 39 | comment: null 40 | - select_permissions: [] 41 | object_relationships: 42 | - using: 43 | manual_configuration: 44 | remote_table: campaigns 45 | column_mapping: 46 | company_id: company_id 47 | campaign_id: id 48 | name: campaign 49 | comment: null 50 | event_triggers: [] 51 | insert_permissions: [] 52 | table: ads 53 | update_permissions: [] 54 | delete_permissions: [] 55 | array_relationships: 56 | - using: 57 | manual_configuration: 58 | remote_table: clicks 59 | column_mapping: 60 | company_id: company_id 61 | id: ad_id 62 | name: clicks 63 | comment: null 64 | - using: 65 | manual_configuration: 66 | remote_table: impressions 67 | column_mapping: 68 | company_id: company_id 69 | id: ad_id 70 | name: impressions 71 | comment: null 72 | - select_permissions: [] 73 | object_relationships: [] 74 | event_triggers: [] 75 | insert_permissions: [] 76 | table: companies 77 | update_permissions: [] 78 | delete_permissions: [] 79 | array_relationships: 80 | - using: 81 | foreign_key_constraint_on: 82 | column: company_id 83 | table: campaigns 84 | name: campaigns 85 | comment: null 86 | - select_permissions: [] 87 | object_relationships: 88 | - using: 89 | manual_configuration: 90 | remote_table: ads 91 | column_mapping: 92 | company_id: company_id 93 | ad_id: id 94 | name: ad 95 | comment: null 96 | event_triggers: [] 97 | insert_permissions: [] 98 | table: impressions 99 | update_permissions: [] 100 | delete_permissions: [] 101 | array_relationships: [] 102 | -------------------------------------------------------------------------------- /citus/metadata_with_view.json: -------------------------------------------------------------------------------- 1 | {"tables":[{"table":"clicks","object_relationships":[{"using":{"manual_configuration":{"remote_table":"ads","column_mapping":{"company_id":"company_id","ad_id":"id"}}},"name":"ad","comment":null}],"array_relationships":[],"insert_permissions":[],"select_permissions":[],"update_permissions":[],"delete_permissions":[]},{"table":"campaigns","object_relationships":[{"using":{"foreign_key_constraint_on":"company_id"},"name":"company","comment":null}],"array_relationships":[],"insert_permissions":[],"select_permissions":[],"update_permissions":[],"delete_permissions":[]},{"table":"ads","object_relationships":[{"using":{"manual_configuration":{"remote_table":"campaigns","column_mapping":{"company_id":"company_id","campaign_id":"id"}}},"name":"campaign","comment":null}],"array_relationships":[],"insert_permissions":[],"select_permissions":[],"update_permissions":[],"delete_permissions":[]},{"table":"companies","object_relationships":[],"array_relationships":[{"using":{"foreign_key_constraint_on":{"column":"company_id","table":"campaigns"}},"name":"all_campaigns","comment":null}],"insert_permissions":[],"select_permissions":[],"update_permissions":[],"delete_permissions":[]},{"table":"impressions","object_relationships":[{"using":{"manual_configuration":{"remote_table":"ads","column_mapping":{"company_id":"company_id","ad_id":"id"}}},"name":"ad","comment":null}],"array_relationships":[],"insert_permissions":[],"select_permissions":[],"update_permissions":[],"delete_permissions":[]},{"table":"campaign_ranks","object_relationships":[{"using":{"manual_configuration":{"remote_table":"companies","column_mapping":{"company_id":"id"}}},"name":"company","comment":null},{"using":{"manual_configuration":{"remote_table":"ads","column_mapping":{"ad_id":"id"}}},"name":"ad","comment":null},{"using":{"manual_configuration":{"remote_table":"campaigns","column_mapping":{"ad_id":"id"}}},"name":"campaign","comment":null}],"array_relationships":[],"insert_permissions":[],"select_permissions":[],"update_permissions":[],"delete_permissions":[]}],"query_templates":[]} --------------------------------------------------------------------------------