| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- package executor
- import "testing"
- // TestSelectColumnTypesGroupBy verifies that GROUP BY projections keep their
- // schema types, so protocol clients can decode timestamp/date columns even
- // though the grouped projection is not a plain table scan.
- func TestSelectColumnTypesGroupBy(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, `CREATE TABLE hit_counts (
- site_id INTEGER NOT NULL,
- path_id INTEGER NOT NULL,
- hour TIMESTAMP NOT NULL,
- total INTEGER NOT NULL
- )`)
- execMust(t, e, `CREATE TABLE hit_stats (
- site_id INTEGER NOT NULL,
- path_id INTEGER NOT NULL,
- day DATE NOT NULL,
- stats TEXT
- )`)
- execMust(t, e, `INSERT INTO hit_counts VALUES (1, 1, '2024-05-06 07:00:00', 3)`)
- execMust(t, e, `INSERT INTO hit_stats VALUES (1, 1, '2024-05-06', '[]')`)
- res := execMust(t, e, `SELECT hour, sum(total) FROM hit_counts GROUP BY hour ORDER BY hour`)
- want := []string{"TIMESTAMP", "TEXT"}
- if len(res.ColumnTypes) != len(want) {
- t.Fatalf("group-by column types = %v, want %v", res.ColumnTypes, want)
- }
- for i := range want {
- if res.ColumnTypes[i] != want[i] {
- t.Fatalf("group-by column types = %v, want %v", res.ColumnTypes, want)
- }
- }
- res = execMust(t, e, `SELECT path_id, day, stats FROM hit_stats ORDER BY day`)
- want = []string{"INTEGER", "DATE", "TEXT"}
- if len(res.ColumnTypes) != len(want) {
- t.Fatalf("date column types = %v, want %v", res.ColumnTypes, want)
- }
- for i := range want {
- if res.ColumnTypes[i] != want[i] {
- t.Fatalf("date column types = %v, want %v", res.ColumnTypes, want)
- }
- }
- }
- // TestSubstrSQLiteSemantics pins SQLite's zero/negative start behavior, which
- // GoatCounter relies on to derive a country code from a region code.
- func TestSubstrSQLiteSemantics(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- cases := []struct {
- sql string
- want string
- }{
- {`SELECT substr('US-NY', 0, 3)`, "US"},
- {`SELECT substr('US-NY', 1, 3)`, "US-"},
- {`SELECT substr('US-NY', 2, 3)`, "S-N"},
- {`SELECT substr('US-NY', -2, 2)`, "NY"},
- {`SELECT substr('US-NY', 4)`, "NY"},
- {`SELECT substr('US-NY', 0)`, "US-NY"},
- }
- for _, tc := range cases {
- res := execMust(t, e, tc.sql)
- if len(res.Rows) != 1 || res.Rows[0][0] != tc.want {
- t.Fatalf("%s = %#v, want %q", tc.sql, res.Rows, tc.want)
- }
- }
- }
- // TestCTEGroupByAlias reproduces the GoatCounter locations query: a CTE that
- // groups by a SELECT alias, with the outer query joining on that alias column.
- func TestCTEGroupByAlias(t *testing.T) {
- _, schema, table := newTestDB(t)
- e := newExec(schema, table)
- execMust(t, e, `CREATE TABLE location_stats (
- site_id INTEGER NOT NULL,
- path_id INTEGER NOT NULL,
- day DATE NOT NULL,
- location TEXT NOT NULL,
- count INTEGER NOT NULL
- )`)
- execMust(t, e, `CREATE TABLE locations (
- iso_3166_2 TEXT NOT NULL,
- country_name TEXT NOT NULL
- )`)
- execMust(t, e, `INSERT INTO location_stats VALUES (1, 1, '2024-05-06', 'US-NY', 4)`)
- execMust(t, e, `INSERT INTO locations VALUES ('US', 'United States')`)
- res := execMust(t, e, `WITH x AS (
- SELECT substr(location, 0, 3) AS loc, sum(count) AS count
- FROM location_stats
- WHERE site_id = 1 AND day >= '2024-01-01' AND day <= '2024-12-31'
- GROUP BY loc
- ORDER BY count DESC, loc
- LIMIT 5
- )
- SELECT locations.iso_3166_2 AS id, locations.country_name AS name, x.count AS count
- FROM x
- JOIN locations ON locations.iso_3166_2 = x.loc
- ORDER BY count DESC, name ASC`)
- if res.RowCount != 1 {
- t.Fatalf("expected 1 row, got %d (%v)", res.RowCount, res.Rows)
- }
- if res.Rows[0][0] != "US" || res.Rows[0][2].(int64) != 4 {
- t.Fatalf("unexpected row %#v", res.Rows[0])
- }
- }
|