@graphenedata/cli 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +3 -3
- package/README.md +138 -0
- package/THIRD_PARTY_NOTICES.md +1 -0
- package/bin.js +2 -2
- package/dist/cli/bigQuery-I3F46SC6.js +75 -0
- package/dist/cli/bigQuery-I3F46SC6.js.map +7 -0
- package/dist/cli/chunk-OVWODUTJ.js +12849 -0
- package/dist/cli/chunk-OVWODUTJ.js.map +7 -0
- package/dist/cli/chunk-QAXEOZ43.js +53 -0
- package/dist/cli/chunk-QAXEOZ43.js.map +7 -0
- package/dist/cli/cli.js +245 -10290
- package/dist/cli/clickhouse-ZN5AN2UL.js +64 -0
- package/dist/cli/clickhouse-ZN5AN2UL.js.map +7 -0
- package/dist/cli/duckdb-IYBIO5KJ.js +87 -0
- package/dist/cli/duckdb-IYBIO5KJ.js.map +7 -0
- package/dist/cli/serve2-TNN5EROW.js +447 -0
- package/dist/cli/serve2-TNN5EROW.js.map +7 -0
- package/dist/cli/snowflake-MOQB5GA4.js +128 -0
- package/dist/cli/snowflake-MOQB5GA4.js.map +7 -0
- package/dist/index.d.ts +63 -0
- package/dist/lang/index.d.ts +63 -0
- package/dist/skills/graphene/SKILL.md +235 -0
- package/dist/skills/graphene/references/big-value.md +20 -0
- package/dist/skills/graphene/references/date-range.md +64 -0
- package/dist/skills/graphene/references/dropdown.md +62 -0
- package/dist/skills/graphene/references/echarts.md +162 -0
- package/dist/skills/graphene/references/gsql.md +393 -0
- package/dist/skills/graphene/references/model-gsql.md +72 -0
- package/dist/skills/graphene/references/table.md +143 -0
- package/dist/skills/graphene/references/text-input.md +29 -0
- package/dist/ui/app.css +263 -299
- package/dist/ui/component-utilities/dataShaping.ts +484 -0
- package/dist/ui/component-utilities/dataSummary.ts +57 -0
- package/dist/ui/component-utilities/enrich.ts +763 -0
- package/dist/ui/component-utilities/format.ts +177 -0
- package/dist/ui/component-utilities/inputUtils.ts +48 -9
- package/dist/ui/component-utilities/theme.ts +200 -0
- package/dist/ui/component-utilities/themeStores.ts +26 -21
- package/dist/ui/component-utilities/types.ts +70 -0
- package/dist/ui/components/AreaChart.svelte +57 -105
- package/dist/ui/components/BarChart.svelte +71 -129
- package/dist/ui/components/BigValue.svelte +24 -40
- package/dist/ui/components/Column.svelte +11 -19
- package/dist/ui/components/DateRange.svelte +71 -34
- package/dist/ui/components/Dropdown.svelte +82 -49
- package/dist/ui/components/DropdownOption.svelte +1 -2
- package/dist/ui/components/ECharts.svelte +179 -60
- package/dist/ui/components/InlineDelta.svelte +51 -32
- package/dist/ui/components/LineChart.svelte +54 -125
- package/dist/ui/components/PieChart.svelte +27 -37
- package/dist/ui/components/QueryLoad.svelte +78 -44
- package/dist/ui/components/Row.svelte +2 -1
- package/dist/ui/components/ScatterPlot.svelte +52 -0
- package/dist/ui/components/Skeleton.svelte +32 -0
- package/dist/ui/components/Table.svelte +3 -2
- package/dist/ui/components/TableGroupRow.svelte +28 -36
- package/dist/ui/components/TableHarness.svelte +32 -0
- package/dist/ui/components/TableHeader.svelte +34 -59
- package/dist/ui/components/TableRow.svelte +15 -39
- package/dist/ui/components/TableSubtotalRow.svelte +26 -21
- package/dist/ui/components/TableTotalRow.svelte +27 -37
- package/dist/ui/components/TextInput.svelte +17 -14
- package/dist/ui/components/Value.svelte +25 -0
- package/dist/ui/components/_Table.svelte +80 -76
- package/dist/ui/internal/ChartGallery.svelte +527 -0
- package/dist/ui/internal/ErrorDisplay.svelte +60 -0
- package/dist/ui/internal/LocalApp.svelte +87 -19
- package/dist/ui/internal/PageNavGroup.svelte +269 -0
- package/dist/ui/internal/Sidebar.svelte +178 -0
- package/dist/ui/internal/SidebarToggle.svelte +47 -0
- package/dist/ui/internal/StyleGallery.svelte +244 -0
- package/dist/ui/internal/clientCache.ts +15 -13
- package/dist/ui/internal/pageInputs.svelte.js +292 -0
- package/dist/ui/internal/queryEngine.ts +124 -132
- package/dist/ui/internal/runSocket.ts +59 -0
- package/dist/ui/internal/sidebar.svelte.js +18 -0
- package/dist/ui/internal/telemetry.ts +52 -17
- package/dist/ui/internal/types.d.ts +7 -0
- package/dist/ui/web.js +55 -13
- package/package.json +40 -41
- package/dist/docs/agent-instructions.md +0 -18
- package/dist/docs/base.md +0 -98
- package/dist/docs/cli.md +0 -22
- package/dist/docs/graphene.md +0 -1462
- package/dist/ui/component-utilities/autoFormatting.js +0 -301
- package/dist/ui/component-utilities/builtInFormats.js +0 -482
- package/dist/ui/component-utilities/chartContext.js +0 -12
- package/dist/ui/component-utilities/chartWindowDebug.js +0 -21
- package/dist/ui/component-utilities/checkInputs.js +0 -95
- package/dist/ui/component-utilities/convert.js +0 -15
- package/dist/ui/component-utilities/dateParsing.js +0 -57
- package/dist/ui/component-utilities/dropdownContext.ts +0 -1
- package/dist/ui/component-utilities/echarts.js +0 -272
- package/dist/ui/component-utilities/echartsThemes.js +0 -453
- package/dist/ui/component-utilities/formatTitle.js +0 -24
- package/dist/ui/component-utilities/formatting.js +0 -250
- package/dist/ui/component-utilities/getColumnExtents.js +0 -79
- package/dist/ui/component-utilities/getColumnSummary.js +0 -67
- package/dist/ui/component-utilities/getCompletedData.js +0 -114
- package/dist/ui/component-utilities/getDistinctCount.js +0 -7
- package/dist/ui/component-utilities/getDistinctValues.js +0 -15
- package/dist/ui/component-utilities/getSeriesConfig.js +0 -237
- package/dist/ui/component-utilities/getSortedData.js +0 -7
- package/dist/ui/component-utilities/getStackPercentages.js +0 -43
- package/dist/ui/component-utilities/getStackedData.js +0 -17
- package/dist/ui/component-utilities/getYAxisIndex.js +0 -15
- package/dist/ui/component-utilities/globalContexts.js +0 -1
- package/dist/ui/component-utilities/helpers/getCompletedData.helpers.js +0 -119
- package/dist/ui/component-utilities/replaceNulls.js +0 -14
- package/dist/ui/component-utilities/tableUtils.ts +0 -120
- package/dist/ui/components/Area.svelte +0 -214
- package/dist/ui/components/Bar.svelte +0 -350
- package/dist/ui/components/Chart.svelte +0 -989
- package/dist/ui/components/ErrorChart.svelte +0 -118
- package/dist/ui/components/Line.svelte +0 -227
- package/dist/ui/internal/NavSidebar.svelte +0 -396
- package/dist/ui/internal/PageError.svelte +0 -23
- package/dist/ui/internal/checkSocket.ts +0 -48
- package/dist/ui/internal/theme.ts +0 -88
- package/dist/ui/public/inter-latin-ext.woff2 +0 -0
- package/dist/ui/public/inter-latin.woff2 +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Creates a date range picker with start/end date inputs and optional preset ranges. The selected range can be used to filter queries or in markdown.
|
|
2
|
+
|
|
3
|
+
Here's an example:
|
|
4
|
+
|
|
5
|
+
````markdown
|
|
6
|
+
```sql sales
|
|
7
|
+
select date, revenue from orders
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
<DateRange
|
|
11
|
+
title="Date Range"
|
|
12
|
+
name=date_filter
|
|
13
|
+
data=sales
|
|
14
|
+
dates=date
|
|
15
|
+
/>
|
|
16
|
+
````
|
|
17
|
+
|
|
18
|
+
The selected start and end dates are then referenced in GSQL as `$date_filter_start` and `$date_filter_end`. For example:
|
|
19
|
+
|
|
20
|
+
```sql
|
|
21
|
+
select date, revenue
|
|
22
|
+
from orders
|
|
23
|
+
where date >= $date_filter_start and date < $date_filter_end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Date range selections sync into the page URL query string as `{name}_start` and `{name}_end`, eg. `localhost:4000/my_dashboard?date_filter_start=2024-01-01&date_filter_end=2024-02-01`.
|
|
27
|
+
|
|
28
|
+
# Attributes
|
|
29
|
+
|
|
30
|
+
| Attribute | Description | Required | Options | Default |
|
|
31
|
+
|------|-------------|----------|---------|---------|
|
|
32
|
+
| name | Name of the date range, used to reference the selected value elsewhere as `"$name.start"` and `"$name.end"` | true | - | - |
|
|
33
|
+
| data | GSQL query or table name to infer the date domain from | false | query name | - |
|
|
34
|
+
| dates | Column name from the query containing date values, used to determine the min/max of the domain | false | column name | - |
|
|
35
|
+
| start | Initial start date | false | date string or Date | - |
|
|
36
|
+
| end | Initial end date | false | date string or Date | - |
|
|
37
|
+
| defaultValue | Preset label to apply on first load (e.g., `"Last 30 Days"`) | false | preset label | - |
|
|
38
|
+
| presetRanges | List of preset range labels to show in the dropdown. Accepts a comma-separated string or an array. | false | preset labels | See defaults below |
|
|
39
|
+
| title | Title to display above the input | false | string | - |
|
|
40
|
+
| description | Subtitle text displayed below the title | false | string | - |
|
|
41
|
+
|
|
42
|
+
# Preset ranges
|
|
43
|
+
|
|
44
|
+
By default, the following presets are available in the dropdown:
|
|
45
|
+
|
|
46
|
+
- Last 7 Days
|
|
47
|
+
- Last 30 Days
|
|
48
|
+
- Last 90 Days
|
|
49
|
+
- Last 365 Days
|
|
50
|
+
- Last Month
|
|
51
|
+
- Last Year
|
|
52
|
+
- Month to Date
|
|
53
|
+
- Month to Today
|
|
54
|
+
- Year to Date
|
|
55
|
+
- Year to Today
|
|
56
|
+
- All Time
|
|
57
|
+
|
|
58
|
+
The `Last N Days` and `Last N Months` patterns are also supported for any number N (e.g., `"Last 14 Days"`, `"Last 6 Months"`).
|
|
59
|
+
|
|
60
|
+
You can override the list with the `presetRanges` prop:
|
|
61
|
+
|
|
62
|
+
```markdown
|
|
63
|
+
<DateRange name="date_filter" presetRanges="Last 7 Days, Last 30 Days, Last Year" />
|
|
64
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Creates a dropdown menu with a list of options that can be selected. The selected option can be used to filter queries or in markdown.
|
|
2
|
+
|
|
3
|
+
Here's an example:
|
|
4
|
+
|
|
5
|
+
````markdown
|
|
6
|
+
```sql statuses
|
|
7
|
+
select distinct status from orders
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
<Dropdown
|
|
11
|
+
title="Select Order Status"
|
|
12
|
+
name="status_dropdown"
|
|
13
|
+
data="statuses"
|
|
14
|
+
value="status"
|
|
15
|
+
defaultValue="Complete"
|
|
16
|
+
/>
|
|
17
|
+
````
|
|
18
|
+
|
|
19
|
+
The user-selected value would then be referenced in GSQL as `$status_dropdown`. For example:
|
|
20
|
+
|
|
21
|
+
```sql
|
|
22
|
+
select *
|
|
23
|
+
from orders
|
|
24
|
+
where status = $status_dropdown
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Dropdown selections also sync into the page URL query string. Single-select dropdowns use one key, and multi-select dropdowns repeat the key for each selected value.
|
|
28
|
+
|
|
29
|
+
# Attributes
|
|
30
|
+
|
|
31
|
+
| Attribute | Description | Required | Options | Default |
|
|
32
|
+
|------|-------------|----------|---------|---------|
|
|
33
|
+
| name | Name of the dropdown, used to reference the selected value elsewhere as `"$name"` | true | - | - |
|
|
34
|
+
| data | GSQL query or table name | false | query name | - |
|
|
35
|
+
| value | Column name from the query containing values to pick from | false | column name | - |
|
|
36
|
+
| multiple | Enables multi-select which returns a list and syncs repeated values into the URL query string | false | `true`, `false` | `false` |
|
|
37
|
+
| defaultValue | Value to use when the dropdown is first loaded. Must be one of the options in the dropdown. Lists supported for multi-select. | false | value from dropdown, list of values e.g. `"Value 1, Value 2"` | - |
|
|
38
|
+
| selectAllByDefault | Selects and returns all values, multiple attribute required | false | `true`, `false` | `false` |
|
|
39
|
+
| noDefault | Stops any default from being selected. Overrides any set `defaultValue`. | false | boolean | `false` |
|
|
40
|
+
| disableSelectAll | Removes the `"Select all"` button. Recommended for large datasets. | false | boolean | `false` |
|
|
41
|
+
| label | Column name from the query containing labels to display instead of the values (e.g., you may want to have the drop-down use `customer_id` as the value, but show `customer_name` to your users) | false | column name | Uses the column in value |
|
|
42
|
+
| title | Title to display above the dropdown | false | string | - |
|
|
43
|
+
| description | Adds an info icon with description tooltip on hover | false | string | - |
|
|
44
|
+
|
|
45
|
+
# DropdownOption sub-component
|
|
46
|
+
|
|
47
|
+
The `DropdownOption` sub-component can be used to manually add options to a dropdown. This is useful to add a default option, or to add options that are not in a query.
|
|
48
|
+
|
|
49
|
+
Here's an example:
|
|
50
|
+
|
|
51
|
+
```markdown
|
|
52
|
+
<Dropdown name=hardcoded>
|
|
53
|
+
<DropdownOption valueLabel="Option One" value=1 />
|
|
54
|
+
<DropdownOption valueLabel="Option Two" value=2 />
|
|
55
|
+
<DropdownOption valueLabel="Option Three" value=3 />
|
|
56
|
+
</Dropdown>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| Attribute | Description | Required | Options | Default |
|
|
60
|
+
|------|-------------|----------|---------|---------|
|
|
61
|
+
| value | Value to use when the option is selected | true | - | - |
|
|
62
|
+
| valueLabel | Label to display for the option in the dropdown | false | - | Uses the value |
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Use `ECharts` when you need chart behavior that goes beyond the built-in chart components.
|
|
2
|
+
|
|
3
|
+
Graphene charts are powered by Apache ECharts (v6). In markdown, you define the ECharts option object **inside the `<ECharts>` tag body**.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
|
|
7
|
+
```markdown
|
|
8
|
+
<ECharts data="sales_by_month">
|
|
9
|
+
title: {text: "Revenue"},
|
|
10
|
+
tooltip: {trigger: "axis"},
|
|
11
|
+
series: [{type: "line", encode: {x: "month", y: "revenue"}}],
|
|
12
|
+
</ECharts>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
# Attributes
|
|
16
|
+
|
|
17
|
+
| Attribute | Description | Required | Options | Default |
|
|
18
|
+
|----------|-------------|----------|---------|---------|
|
|
19
|
+
| data | GSQL query or table name | true | query/table name | - |
|
|
20
|
+
| height | Chart height in px or CSS size string | false | number, string | `240px` |
|
|
21
|
+
| width | Chart width in px or CSS size string | false | number, string | `100%` |
|
|
22
|
+
| renderer | ECharts renderer | false | `svg`, `canvas` | `svg` |
|
|
23
|
+
|
|
24
|
+
## Config body syntax
|
|
25
|
+
|
|
26
|
+
Inside `<ECharts>...</ECharts>`, Graphene parses the config as JSON5:
|
|
27
|
+
- Unquoted keys are allowed (`xAxis: {}`)
|
|
28
|
+
- Trailing commas are allowed
|
|
29
|
+
- You can wrap the whole thing in `{ ... }` or omit the outer braces
|
|
30
|
+
- Inline Javascript is not allowed
|
|
31
|
+
|
|
32
|
+
## What Graphene handles automatically
|
|
33
|
+
|
|
34
|
+
You don't need to configure these — Graphene applies them by default:
|
|
35
|
+
|
|
36
|
+
- Axes: created if missing, types inferred from field metadata (time, category, value), tick formatting applied
|
|
37
|
+
- Layout: grid padding computed to prevent title/legend overlap
|
|
38
|
+
- Style: color palette, fonts, axis borders, split lines, and series marker defaults (via the Graphene theme)
|
|
39
|
+
|
|
40
|
+
Your config typically only needs to specify the series `type`, `encode` mappings, and any explicit overrides to the above.
|
|
41
|
+
|
|
42
|
+
## Encode fields by series type
|
|
43
|
+
|
|
44
|
+
Each series type maps columns via `encode`. Graphene accepts:
|
|
45
|
+
|
|
46
|
+
| Series type | Encode fields |
|
|
47
|
+
|-------------|---------------|
|
|
48
|
+
| `bar`, `line`, `scatter`, `candlestick`, `heatmap`, `effectScatter` | `x`, `y`, `splitBy` |
|
|
49
|
+
| `pie`, `funnel` | `itemName`, `value` |
|
|
50
|
+
| `treemap` | `itemName`, `value` |
|
|
51
|
+
| `sankey`, `chord` | `source`, `target`, `value` |
|
|
52
|
+
| `themeRiver` | `single`, `value`, `seriesName` |
|
|
53
|
+
|
|
54
|
+
For a beeswarm, use a `scatter` series and set `jitter` (plus optional `jitterOverlap`/`jitterMargin`) on the categorical axis.
|
|
55
|
+
|
|
56
|
+
## Customizing with split hints
|
|
57
|
+
|
|
58
|
+
To keep configs concise, Graphene supports a split hint:
|
|
59
|
+
|
|
60
|
+
- `encode.splitBy: "field"`: split one series template into one series per distinct field value
|
|
61
|
+
- `encode.splitBy: ["groupField", "stackField"]` (bar only): expands to grouped+stacked bars, where the first field groups and the second stacks
|
|
62
|
+
- with a single split field, `series.stack` decides stacked vs grouped behavior
|
|
63
|
+
- `stackPercentage: true`: convert stacked values to percentages (100% stacked)
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
|
|
67
|
+
```markdown
|
|
68
|
+
<ECharts data="sales_by_month_and_region">
|
|
69
|
+
title: {text: "Revenue by Region"},
|
|
70
|
+
series: [{
|
|
71
|
+
type: 'bar',
|
|
72
|
+
encode: {x: 'month', y: 'revenue', splitBy: 'region'},
|
|
73
|
+
stack: 'revenue-stack',
|
|
74
|
+
stackPercentage: true
|
|
75
|
+
}]
|
|
76
|
+
</ECharts>
|
|
77
|
+
|
|
78
|
+
<!-- Char that is both grouped by region and stacked by channel -->
|
|
79
|
+
<ECharts data="sales_by_month_region_channel">
|
|
80
|
+
title: {text: "Revenue by Region and Channel"},
|
|
81
|
+
series: [{
|
|
82
|
+
type: 'bar',
|
|
83
|
+
encode: {x: 'month', y: 'revenue', splitBy: ['region', 'channel']},
|
|
84
|
+
stack: 'revenue-stack',
|
|
85
|
+
stackPercentage: true
|
|
86
|
+
}]
|
|
87
|
+
/>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## More examples
|
|
91
|
+
|
|
92
|
+
### Heatmap
|
|
93
|
+
|
|
94
|
+
```markdown
|
|
95
|
+
<ECharts data="delay_by_hour_and_day" height=520px>
|
|
96
|
+
title: {text: "Avg Delay by Hour & Day of Week (min)"},
|
|
97
|
+
tooltip: {trigger: 'item'},
|
|
98
|
+
visualMap: {
|
|
99
|
+
min: -5, max: 30,
|
|
100
|
+
calculable: true,
|
|
101
|
+
orient: 'horizontal',
|
|
102
|
+
left: 'center',
|
|
103
|
+
bottom: 4,
|
|
104
|
+
inRange: {color: ['#5B8F9E', '#e4eff3', '#D4A94C']},
|
|
105
|
+
},
|
|
106
|
+
xAxis: {type: 'category', position: 'top', data: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']},
|
|
107
|
+
yAxis: {type: 'category', inverse: true, data: ['6am','7am','8am','9am','10am','11am','12pm']},
|
|
108
|
+
series: [{type: 'heatmap', encode: {x: 'day_label', y: 'hour_label', value: 'avg_delay'}}],
|
|
109
|
+
</ECharts>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Scatter/bubble
|
|
113
|
+
|
|
114
|
+
```markdown
|
|
115
|
+
<ECharts data="airport_stats" height=480px>
|
|
116
|
+
title: {text: "Departure vs Arrival Delay by Airport"},
|
|
117
|
+
tooltip: {trigger: 'item'},
|
|
118
|
+
visualMap: {
|
|
119
|
+
dimension: 'flight_count',
|
|
120
|
+
type: 'continuous',
|
|
121
|
+
min: 0, max: 3000,
|
|
122
|
+
inRange: {symbolSize: [4, 32]},
|
|
123
|
+
show: false,
|
|
124
|
+
},
|
|
125
|
+
xAxis: {type: 'value', name: 'Avg Departure Delay (min)', nameLocation: 'middle', nameGap: 22},
|
|
126
|
+
yAxis: {type: 'value', name: 'Avg Arrival Delay (min)', nameLocation: 'middle', nameGap: 20},
|
|
127
|
+
series: [{
|
|
128
|
+
type: 'scatter',
|
|
129
|
+
encode: {x: 'avg_dep_delay', y: 'avg_arr_delay', itemName: 'code'},
|
|
130
|
+
tooltip: {formatter: '{b}'},
|
|
131
|
+
}],
|
|
132
|
+
</ECharts>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Lollipop
|
|
136
|
+
|
|
137
|
+
Combine a `pictorialBar` series (the stem) with a `scatter` series (the dot) on a category y-axis.
|
|
138
|
+
|
|
139
|
+
```markdown
|
|
140
|
+
<ECharts data="avg_delay_by_carrier">
|
|
141
|
+
title: {text: "Avg Delay by Carrier (min)"},
|
|
142
|
+
xAxis: {type: 'value'},
|
|
143
|
+
yAxis: {type: 'category', inverse: true, encode: {y: 'carrier'}},
|
|
144
|
+
series: [
|
|
145
|
+
{
|
|
146
|
+
type: 'pictorialBar',
|
|
147
|
+
symbol: 'rect',
|
|
148
|
+
symbolSize: ['100%', 2],
|
|
149
|
+
symbolPosition: 'end',
|
|
150
|
+
encode: {x: 'avg_delay', y: 'carrier'},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
type: 'scatter',
|
|
154
|
+
symbolSize: 12,
|
|
155
|
+
encode: {x: 'avg_delay', y: 'carrier', itemName: 'carrier'},
|
|
156
|
+
label: {show: true, position: 'right', formatter: '{b}', fontSize: 11},
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
</ECharts>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
For common chart types, prefer `BarChart`, `LineChart`, `AreaChart`, `ScatterPlot`, and `PieChart`. Use `ECharts` when you need deeper customization.
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# Graphene SQL (GSQL)
|
|
2
|
+
|
|
3
|
+
GSQL abstracts over the underlying database SQL. Graphene translates GSQL into database SQL when running queries.
|
|
4
|
+
|
|
5
|
+
GSQL is comprised of four primary statements: `table`, `select`, `table X as`, and `extend`.
|
|
6
|
+
|
|
7
|
+
## `table` statements
|
|
8
|
+
|
|
9
|
+
`table` statements declare tables that already exist in your database. Here's an example of two tables, `orders` and `users`, in GSQL.
|
|
10
|
+
|
|
11
|
+
```sql
|
|
12
|
+
table orders (
|
|
13
|
+
|
|
14
|
+
-- Base columns
|
|
15
|
+
|
|
16
|
+
id BIGINT
|
|
17
|
+
user_id BIGINT
|
|
18
|
+
created_at DATETIME
|
|
19
|
+
status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
|
|
20
|
+
amount FLOAT -- Amount paid by customer #units=usd
|
|
21
|
+
cost FLOAT -- Cost of materials #units=usd
|
|
22
|
+
|
|
23
|
+
-- Join relationships
|
|
24
|
+
|
|
25
|
+
join one users on user_id = users.id
|
|
26
|
+
|
|
27
|
+
-- Dimensions
|
|
28
|
+
|
|
29
|
+
revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
|
|
30
|
+
|
|
31
|
+
-- Measures
|
|
32
|
+
|
|
33
|
+
revenue: sum(case when revenue_recognized then amount else 0 end) #units=usd
|
|
34
|
+
cogs: sum(case when revenue_recognized then cost else 0 end) #units=usd
|
|
35
|
+
profit: revenue - cogs #units=usd
|
|
36
|
+
profit_margin: profit / revenue #ratio
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
table users (
|
|
40
|
+
id BIGINT
|
|
41
|
+
name VARCHAR
|
|
42
|
+
email VARCHAR
|
|
43
|
+
age INTEGER
|
|
44
|
+
country_code VARCHAR
|
|
45
|
+
|
|
46
|
+
join many orders on id = orders.user_id
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
We can break down a `table` statement into three parts: base columns, join relationships, and stored expressions (dimensions and measures).
|
|
51
|
+
|
|
52
|
+
### Base columns (required)
|
|
53
|
+
|
|
54
|
+
The base column set is simply a reflection of the underlying database table's schema. Similar to `create table` statements in regular SQL DDL, you list each column's name and data type.
|
|
55
|
+
|
|
56
|
+
For array columns, GSQL uses `array<T>` as the standard syntax:
|
|
57
|
+
|
|
58
|
+
```sql
|
|
59
|
+
table events (
|
|
60
|
+
id BIGINT
|
|
61
|
+
tags array<string>
|
|
62
|
+
scores array<int64>
|
|
63
|
+
)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Join relationships
|
|
67
|
+
|
|
68
|
+
Join relationships in a `table` statement declare joins that can be used when querying them. This makes query writing easier and more foolproof.
|
|
69
|
+
|
|
70
|
+
The other main difference about joins in GSQL vs. regular SQL is that you have to explain if there are many rows in the left table for each row in the right table, or vice versa. This additional bit of information allows Graphene to prevent incorrect aggregation as a result of row duplication (aka fan-out) through joins.
|
|
71
|
+
|
|
72
|
+
This information is provided with the two supported join types, `join one` and `join many`:
|
|
73
|
+
- `join one` is used when each row in **this** table maps to at most one row in the **joined** table.
|
|
74
|
+
- `join many` is used when each row in **this** table can map to many rows in the **joined** table.
|
|
75
|
+
|
|
76
|
+
In the example above with `orders` and `users`, the joins confirm that there are many orders per user, and only one user per order.
|
|
77
|
+
|
|
78
|
+
Note that all joins in GSQL are left outer joins. There is no inner, right, or cross join.
|
|
79
|
+
|
|
80
|
+
#### Multiple join relationships between the same two tables
|
|
81
|
+
|
|
82
|
+
Sometimes there are multiple valid ways to join two tables together. You can model this in Graphene by aliasing the various joins with `as`, just as you would in normal SQL. For example:
|
|
83
|
+
|
|
84
|
+
```sql
|
|
85
|
+
table projects (
|
|
86
|
+
...
|
|
87
|
+
owner_id BIGINT
|
|
88
|
+
viewer_id BIGINT
|
|
89
|
+
|
|
90
|
+
join one users as project_owner on owner_id = project_owner.id
|
|
91
|
+
join one users as project_viewer on viewer_id = project_viewer.id
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
table users (
|
|
95
|
+
...
|
|
96
|
+
id BIGINT
|
|
97
|
+
|
|
98
|
+
join many projects as projects_as_owner on id = projects_as_owner.owner_id
|
|
99
|
+
join many projects as projects_as_viewer on id = projects_as_viewer.viewer_id
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Best practices for modeling join relationships
|
|
104
|
+
|
|
105
|
+
- For a given `table` statement, only model joins that are directly on that table. Multi-hop join paths do not need to be written explicitly in order for queries to traverse them.
|
|
106
|
+
- A join between two tables should be modeled in both the respective `table` statements. This may seem redundant but it offers more flexibility for queries to choose which table to set in the `from` (remember that direction matters in queries since all joins are left joins).
|
|
107
|
+
|
|
108
|
+
### Stored expressions
|
|
109
|
+
|
|
110
|
+
**Stored expressions** are GSQL expressions (ie. any arbitrary combination of functions, operators, and column references) that you want to make reusable to queries. Stored expressions are great for canonizing metrics, segments, and other important business definitions.
|
|
111
|
+
|
|
112
|
+
A stored expression must be given a name via `name: expression` or `expression as name`. It can then be referenced by name in queries that use the table.
|
|
113
|
+
|
|
114
|
+
Like expressions in regular SQL, expressions in GSQL are either scalar or aggregative. In BI parlance, these would be called dimensions and measures, respectively.
|
|
115
|
+
|
|
116
|
+
Expressions can refer to other expressions, as from the example before:
|
|
117
|
+
|
|
118
|
+
```sql
|
|
119
|
+
table orders (
|
|
120
|
+
...
|
|
121
|
+
|
|
122
|
+
/* Scalar expressions */
|
|
123
|
+
revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
|
|
124
|
+
|
|
125
|
+
/* Agg expressions */
|
|
126
|
+
revenue: sum(case when revenue_recognized then amount else 0 end) #units=usd
|
|
127
|
+
cogs: sum(case when revenue_recognized then cost else 0 end) #units=usd
|
|
128
|
+
profit: revenue - cogs #units=usd -- even though there are no agg functions here, this is still aggregative as it references other aggregative expressions
|
|
129
|
+
profit_margin: profit / revenue #ratio
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Metadata annotations
|
|
134
|
+
|
|
135
|
+
Certain GSQL expressions will create field-level metadata that Graphene uses for better formatting and visualizations defaults. For example, `date_trunc('month', created_at)` will attach `timeGrain=month` metadata to the resulting column, which informs Graphene that the values represent months and not just days that happen to land on the first of every month. Graphene leverages this information to make sure that value formatting and chart axes look good by default.
|
|
136
|
+
|
|
137
|
+
There isn't always a SQL expression that can tip Graphene to the semantic meaning of a field, however:
|
|
138
|
+
- The field could be a base column that has no source expression
|
|
139
|
+
- There might not be enough information in the expression (eg. what currency a float is tied to)
|
|
140
|
+
|
|
141
|
+
For this reason, some metadata should be set explicitly in the GSQL model, using annotations. Metadata annotations resemble hashtags (eg. `#ratio`, `#units=usd`) that can be inlined or written above the object they decorate.
|
|
142
|
+
|
|
143
|
+
#### Recognized metadata
|
|
144
|
+
|
|
145
|
+
| Key | Inferrable? | Effect |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `#ratio` | no | Value is 0–1; rendered as `value × 100%` (e.g. `0.42` → `42%`) |
|
|
148
|
+
| `#pct` | no | Value is already 0–100; rendered as `value%` (e.g. `42` → `42%`) |
|
|
149
|
+
| `#units=<currency>` | no | Adds currency symbol and compacts to K/M/B. Accepted values: `usd`, `eur`, `gbp`, `cad`, `aud`, `jpy` |
|
|
150
|
+
| `#timeGrain=<grain>` | yes (from `date_trunc`, `date_bin`, casts) | Controls time axis label format. Values: `year`, `quarter`, `month`, `week`, `day`, `hour`, `minute`, `second` |
|
|
151
|
+
| `#timeOrdinal=<ordinal>` | yes (from `extract`) | Treats values as ordinal positions on a categorical axis. Values: `hour_of_day`, `day_of_month`, `day_of_year`, `week_of_year`, `month_of_year`, `quarter_of_year`, `dow_0s` (0=Sun), `dow_1s` (1=Sun), `dow_1m` (1=Mon) |
|
|
152
|
+
|
|
153
|
+
## `select` statements
|
|
154
|
+
|
|
155
|
+
`select` in GSQL has been extended to leverage join relationships, dimensions, and measures.
|
|
156
|
+
|
|
157
|
+
### Using join relationships
|
|
158
|
+
|
|
159
|
+
If a `table` has join relationships declared in it, a `select` query on that table can leverage that join without needing to write its own join statement. This is helpful for query writers who have not memorized all the correct join keys.
|
|
160
|
+
|
|
161
|
+
If you recall the model from before:
|
|
162
|
+
|
|
163
|
+
```sql
|
|
164
|
+
table orders (
|
|
165
|
+
...
|
|
166
|
+
user_id BIGINT
|
|
167
|
+
join one users on user_id = users.id
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
table users (
|
|
171
|
+
id BIGINT
|
|
172
|
+
name VARCHAR
|
|
173
|
+
...
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
We can write a query that leverages the modeled join relationship between `orders` and `users`:
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
```sql
|
|
181
|
+
-- Top 10 customers by order count
|
|
182
|
+
select
|
|
183
|
+
users.name, -- Use the dot operator to traverse the modeled join relationship
|
|
184
|
+
count(*)
|
|
185
|
+
from orders -- A join statement here is not needed
|
|
186
|
+
group by 1
|
|
187
|
+
order by 2 desc
|
|
188
|
+
limit 10
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### Multi-hop joins
|
|
192
|
+
|
|
193
|
+
Sometimes you need to access columns or stored expressions in a table that is two or more joins away from the `from` table. To do this, simply use more dot operators to trace the desired join path. For example, say there is another table added to our project, `countries`:
|
|
194
|
+
|
|
195
|
+
```sql
|
|
196
|
+
table orders (
|
|
197
|
+
...
|
|
198
|
+
|
|
199
|
+
join one users on user_id = users.id
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
table users (
|
|
203
|
+
...
|
|
204
|
+
|
|
205
|
+
join many orders on id = orders.user_id
|
|
206
|
+
join one countries on country_code = countries.code
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
table countries (
|
|
210
|
+
code VARCHAR
|
|
211
|
+
name VARCHAR
|
|
212
|
+
currency VARCHAR
|
|
213
|
+
free_shipping BOOLEAN
|
|
214
|
+
|
|
215
|
+
join many users on code = users.country_code
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
We can write the following query to show the top ten countries by order count:
|
|
220
|
+
|
|
221
|
+
```sql
|
|
222
|
+
-- Top 10 countries by order count
|
|
223
|
+
select
|
|
224
|
+
users.countries.name, -- Orders -> Users -> Countries
|
|
225
|
+
count(*)
|
|
226
|
+
from orders
|
|
227
|
+
group by 1
|
|
228
|
+
order by 2 desc
|
|
229
|
+
limit 10
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Using stored expressions in queries
|
|
233
|
+
|
|
234
|
+
A stored expression can be invoked in a query by simply referencing it by name.
|
|
235
|
+
|
|
236
|
+
Again, using the orders table from before:
|
|
237
|
+
|
|
238
|
+
```sql
|
|
239
|
+
table orders (
|
|
240
|
+
id BIGINT
|
|
241
|
+
user_id BIGINT
|
|
242
|
+
created_at DATETIME
|
|
243
|
+
status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
|
|
244
|
+
amount FLOAT -- Amount paid by customer #units=usd
|
|
245
|
+
cost FLOAT -- Cost of materials #units=usd
|
|
246
|
+
|
|
247
|
+
join one users on user_id = users.id
|
|
248
|
+
|
|
249
|
+
revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
|
|
250
|
+
revenue: sum(case when revenue_recognized then amount else 0 end) #units=usd
|
|
251
|
+
cogs: sum(case when revenue_recognized then cost else 0 end) #units=usd
|
|
252
|
+
profit: revenue - cogs #units=usd
|
|
253
|
+
profit_margin: profit / revenue #ratio
|
|
254
|
+
)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
We can count the number of orders that were revenue-recognized vs. not:
|
|
258
|
+
|
|
259
|
+
```sql
|
|
260
|
+
-- Number of revenue-recognized orders vs. not
|
|
261
|
+
select
|
|
262
|
+
revenue_recognized, -- Stored expression in orders
|
|
263
|
+
count(*)
|
|
264
|
+
from orders
|
|
265
|
+
group by 1
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
This would be equivalent to:
|
|
269
|
+
|
|
270
|
+
```sql
|
|
271
|
+
select
|
|
272
|
+
status in ('Processing', 'Shipped', 'Complete') as revenue_recognized,
|
|
273
|
+
count(*)
|
|
274
|
+
from orders
|
|
275
|
+
group by 1
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
You can see that invoking a stored expression is like using a macro: the definition for the stored expression is effectively expanded in-line by Graphene when it runs the query.
|
|
279
|
+
|
|
280
|
+
#### Using measures
|
|
281
|
+
|
|
282
|
+
The macro concept is important to understand when invoking stored expressions that are **aggregative** (ie. contain agg functions), which can also be called "measures." Here's an example.
|
|
283
|
+
|
|
284
|
+
```sql
|
|
285
|
+
-- Profit by status
|
|
286
|
+
select
|
|
287
|
+
status,
|
|
288
|
+
profit
|
|
289
|
+
from orders
|
|
290
|
+
group by 1
|
|
291
|
+
order by 1 asc
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Note that, while `profit` looks like a column here, it is _not_ a column. That's because this query is equivalent to:
|
|
295
|
+
|
|
296
|
+
```sql
|
|
297
|
+
select
|
|
298
|
+
status,
|
|
299
|
+
sum(case when revenue_recognized then amount else 0 end) - sum(case when revenue_recognized then cost else 0 end) as profit -- Profit is defined as revenue - cogs, which respectively expands out to these two filtered sums
|
|
300
|
+
from orders
|
|
301
|
+
group by 1
|
|
302
|
+
order by 1 asc
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
[CRITICAL!] This means:
|
|
306
|
+
- You would NEVER wrap a measure in an agg function like `sum(my_measure)`, for the same reason that you cannot do `SUM(SUM(foo))` in regular SQL.
|
|
307
|
+
- You would NEVER group by a measure like `group by my_measure`, for the same reason that you cannot do `GROUP BY SUM(foo)` in regular SQL.
|
|
308
|
+
- You CAN wrap a measure in a scalar function like `floor(my_measure)`, for the same reason that can do `FLOOR(SUM(foo))` in regular SQL.
|
|
309
|
+
- You CAN compose measures together in expressions like `my_measure + my_other_measure`, for the same reason that you can do `SUM(foo) + SUM(bar)` in regular SQL.
|
|
310
|
+
|
|
311
|
+
Another way of thinking about this is that measures are "self-aggregating."
|
|
312
|
+
|
|
313
|
+
### Other miscellaneous details
|
|
314
|
+
|
|
315
|
+
- The clauses in a `select` statement (`select`, `from`, `join`, `group by`, etc.) can be written in any order. They cannot be repeated, however.
|
|
316
|
+
- `group by all` is implied if aggregative and scalar expressions are both present in the `select` clause. This means that `group by` can be omitted and the query will still effectively execute the `group by all`.
|
|
317
|
+
- Expressions in `group by` are implicitly selected, so `from orders select avg(amount) group by user_id` will return two columns.
|
|
318
|
+
- `count` is a reserved word. Do not alias your columns as `count`.
|
|
319
|
+
- Inline window functions are supported using ANSI-style `OVER (...)` clauses. Query-level named windows (`WINDOW w AS (...)`) are not supported.
|
|
320
|
+
- Percentiles can be computed easily using Graphene's special functions `pXX(col)` (e.g., p50, p975, p9999).
|
|
321
|
+
- Graphene supports almost all functions of the connected data warehouse. Check package.json to see which database you're connected to.
|
|
322
|
+
|
|
323
|
+
## `table X as` statements
|
|
324
|
+
|
|
325
|
+
You can turn the output of any `select` statement into a table with `table foo as (select ...)`. Here's an example of an additional table `user_facts` added to the two tables from earlier:
|
|
326
|
+
|
|
327
|
+
```sql
|
|
328
|
+
table orders (
|
|
329
|
+
id BIGINT
|
|
330
|
+
user_id BIGINT
|
|
331
|
+
created_at DATETIME
|
|
332
|
+
status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
|
|
333
|
+
amount FLOAT -- Amount paid by customer #units=usd
|
|
334
|
+
cost FLOAT -- Cost of materials #units=usd
|
|
335
|
+
|
|
336
|
+
join one users on user_id = users.id
|
|
337
|
+
|
|
338
|
+
revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
|
|
339
|
+
revenue: sum(case when revenue_recognized then amount else 0 end) #units=usd
|
|
340
|
+
cogs: sum(case when revenue_recognized then cost else 0 end) #units=usd
|
|
341
|
+
profit: revenue - cogs #units=usd
|
|
342
|
+
profit_margin: profit / revenue #ratio
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
table users (
|
|
346
|
+
id BIGINT
|
|
347
|
+
name VARCHAR
|
|
348
|
+
email VARCHAR
|
|
349
|
+
age INTEGER
|
|
350
|
+
|
|
351
|
+
join many orders on id = orders.user_id
|
|
352
|
+
join one user_facts on id = user_facts.id
|
|
353
|
+
|
|
354
|
+
ltv: user_facts.ltv #units=usd
|
|
355
|
+
lifetime_orders: user_facts.lifetime_orders
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
table user_facts as (
|
|
359
|
+
select id, orders.revenue as ltv, count(orders.id) as lifetime_orders,
|
|
360
|
+
from users group by id
|
|
361
|
+
)
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`table X as` statements are conceptually the same as view tables and CTEs in regular SQL. A few things to note:
|
|
365
|
+
- You cannot declare join relationships or stored expressions directly in a `table X as` statement. Use an `extend` statement.
|
|
366
|
+
- In the example above, the `ltv` and `lifetime_orders` columns from `user_facts` are "hoisted" back into `users` so that they appear as if they are columns from `users`. This is simply a design choice which allows query writers to never need to know about `user_facts`.
|
|
367
|
+
|
|
368
|
+
## `extend` statements
|
|
369
|
+
|
|
370
|
+
`extend` statements allow you to add join relationships or stored expressions to an existing table. This is especially useful for tables created via `table X as` statements, which do not support defining these properties directly.
|
|
371
|
+
|
|
372
|
+
For example, if we have a `table X as` statement that creates a daily summary of orders:
|
|
373
|
+
|
|
374
|
+
```sql
|
|
375
|
+
table regional_orders as (
|
|
376
|
+
select
|
|
377
|
+
region,
|
|
378
|
+
count(*) as num_orders,
|
|
379
|
+
sum(amount) as total_revenue
|
|
380
|
+
from orders
|
|
381
|
+
group by 1
|
|
382
|
+
)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
We can extend this table to add measures or joins:
|
|
386
|
+
|
|
387
|
+
```sql
|
|
388
|
+
extend regional_orders (
|
|
389
|
+
join one regions on region = regions.name
|
|
390
|
+
|
|
391
|
+
avg_order_value: total_revenue / num_orders #units=usd
|
|
392
|
+
)
|
|
393
|
+
```
|