Skip to content
Open
Show file tree
Hide file tree
Changes from 102 commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
8148077
feat: Implementing date_part
devanbenz Dec 1, 2025
017d816
feat: Add date_part constants and validation
devanbenz Dec 2, 2025
4e4ecad
feat: Adds date_part built in function
devanbenz Dec 3, 2025
fe5551b
feat: Implement date_part builtin function
devanbenz Dec 3, 2025
2da8c6d
chore: checkfmt
devanbenz Dec 3, 2025
70314d7
fix: fixes TestDatePartValuer test
devanbenz Dec 4, 2025
5ce1eb5
feat: Use type cast instead of switch, we only expect StringLiteral
devanbenz Dec 4, 2025
a18ae51
feat: Update ExtractDatePartExpr to return int64 instead of interface
devanbenz Dec 4, 2025
7b2fb86
feat: Working on it
devanbenz Dec 5, 2025
fc6ccf0
feat: Trying ti implement `SELECT date_part` semantics
devanbenz Dec 8, 2025
47c37f5
feat: modify selector for date_part so it allows multiple calls
devanbenz Dec 8, 2025
5015287
feat: Remove nil reference, create global config for IsDatePart
devanbenz Dec 9, 2025
9dd9f89
feat: Use constants for date_part and date_part_time
devanbenz Dec 9, 2025
5f007cf
feat: Add tests for multiple function calls
devanbenz Dec 9, 2025
993837e
feat: Adds sub-query tests
devanbenz Dec 9, 2025
d38787b
chore: prune some bad tests
devanbenz Dec 9, 2025
b1b1d0b
feat: Simplify code
devanbenz Dec 9, 2025
7ad056d
feat: Update Validation for date_part to not return other members
devanbenz Dec 9, 2025
7c1591a
feat: re-add validatedatepart
devanbenz Dec 9, 2025
4591a19
chore: simplify code
devanbenz Dec 9, 2025
48f2235
feat: Update cursor map for time ref
devanbenz Dec 10, 2025
c9d8d4c
feat: Merge branch 'master-1.x' into db/76/date_part
devanbenz Dec 10, 2025
cb92353
feat: Update constants, add invalid enum, check if using date_part wh…
devanbenz Dec 10, 2025
ba00755
fix: fixes ParseDateExpr tests for Invalid enum
devanbenz Dec 10, 2025
4489571
feat: Update TimeString const, add test for DatePartValuer.Value
devanbenz Dec 10, 2025
578b1be
feat: Update consts
devanbenz Dec 10, 2025
995782a
feat: Update iterator key
devanbenz Dec 11, 2025
2bd3738
feat: Adds mapTimeRef bool to help with setting up iterator map
devanbenz Dec 11, 2025
0fee116
feat: Add comment about needTimeRef
devanbenz Dec 11, 2025
d21f33a
feat: Put needTimeRef inside iterator code
devanbenz Dec 11, 2025
7b0acac
fix: remove bad param
devanbenz Dec 11, 2025
985021f
feat: Working on it
devanbenz Dec 19, 2025
d63b5e5
feat: remove some prints
devanbenz Dec 26, 2025
643b871
feat: Merge branch 'master-1.x' into db/76/date_part
devanbenz Feb 17, 2026
42c0afa
feat: working on it
devanbenz Feb 23, 2026
740dba7
feat: Merge branch 'master-1.x' into db/76/date_part
devanbenz Mar 3, 2026
1ad747c
feat: Adds date part handling
devanbenz Mar 3, 2026
8c24c3d
feat: remove shell script for inserting test data locally
devanbenz Mar 3, 2026
1a5c276
feat: stringer
devanbenz Mar 3, 2026
469d5ed
fix: rm fmt
devanbenz Mar 3, 2026
45fbad9
feat: working on tests
devanbenz Mar 4, 2026
0d74f32
fix: Fixes some tests after grouping keys and date_part GROUP BY changes
devanbenz Mar 4, 2026
ecfdb90
feat: remove dead code, don't preempt map init, correct sorting for g…
devanbenz Mar 4, 2026
f1159ad
feat: Add isodow group by tests
devanbenz Mar 4, 2026
1621404
feat: cache needs time ref, check nil for Valuer, formatting
devanbenz Mar 4, 2026
d17f3aa
feat: generate
devanbenz Mar 4, 2026
11657ba
feat: working on it
devanbenz Mar 18, 2026
dd2bfff
feat: adds group by for date_part that allows multiple items
devanbenz Mar 19, 2026
0b34ce2
feat: Merge branch 'master-1.x' into db/76/date_part
devanbenz Mar 19, 2026
b9acf96
feat: Adds dimension grouper interface to generalize group by keys
devanbenz Mar 19, 2026
587b5e2
feat: fix test expectations
devanbenz Mar 19, 2026
0cfc64a
feat: Adds comment about what we are doing in the iterator
devanbenz Mar 19, 2026
012f8cf
feat: re-add accidently deleted comments
devanbenz Mar 19, 2026
f5f9b95
feat: stop unbounded aux key growth, fix many small allocations for p…
devanbenz Mar 19, 2026
0f40612
feat: use testify
devanbenz Mar 19, 2026
64ed350
feat: fmt
devanbenz Mar 19, 2026
b42c1a6
fix: resolve data race with EncodeKeys
devanbenz Mar 19, 2026
41ab550
feat: Simplify the code a bit
devanbenz Mar 20, 2026
b94cbd9
fix: fix test expectation
devanbenz Mar 20, 2026
a2500a8
feat: Modify aux encoded values to ensure that we don't try to access…
devanbenz Mar 23, 2026
3793006
feat: Use a set for the grouping keys
devanbenz Mar 23, 2026
403c0e8
feat: Modify rows
devanbenz Mar 24, 2026
e281f16
feat: Update tests, make sure TZ changes work
devanbenz Jun 16, 2026
afa74a6
fix: resolve some copilot suggestions
devanbenz Jun 16, 2026
8adeee7
chore: typos
devanbenz Jun 16, 2026
d813d2b
fix: Resolves issue with select/group by differences in date_part
devanbenz Jun 16, 2026
6802cb0
feat: regenerate go files
devanbenz Jun 17, 2026
12da3ff
feat: revert protobuf files, dangit claude!
devanbenz Jun 17, 2026
1e85703
fix: resolve typo for isodow
devanbenz Jun 17, 2026
87adc2c
fix: Resolves nested date_part rejection
devanbenz Jun 17, 2026
cd0241f
feat: Use v.table instead of Aux for date part symbols
devanbenz Jun 17, 2026
0c5cc61
fix: return error from DecodeEntry instead of silently dropping
devanbenz Jun 17, 2026
2f822f6
feat: optimize dimension extraction to happen post where filtering
devanbenz Jun 17, 2026
1852457
chore: go generate
devanbenz Jun 17, 2026
73134ff
fix: Address copilot comments
devanbenz Jun 17, 2026
a1eb8d7
fix: address copilot review
devanbenz Jun 17, 2026
57ccfa4
chore: go generate
devanbenz Jun 17, 2026
460955e
feat: Update remote messages for date_part
devanbenz Jun 18, 2026
7f3cd9e
feat: Clean up the code a bit and remove some redundant code
devanbenz Jun 18, 2026
9041f49
feat: Modify date_part to reject nil anchors
devanbenz Jun 18, 2026
ca1a564
chore: template formatting
devanbenz Jun 18, 2026
457099b
feat: copilot suggestions
devanbenz Jun 18, 2026
6360160
fix: copy in to fixed buffer instead of heap allocation
devanbenz Jun 18, 2026
64b5c5e
chore: fix comment
devanbenz Jun 18, 2026
3a5418b
fix: re-add function declaration
devanbenz Jun 22, 2026
ce82e3a
feat: Reduce date_part complexity in other SELECT queries
devanbenz Jun 22, 2026
81a9040
feat: Reject sub-query sources for date_part
devanbenz Jun 23, 2026
8fd2bf7
feat: simplify subquery check
devanbenz Jun 23, 2026
1206e46
feat: simplify
devanbenz Jun 23, 2026
84c46dc
fix: defensive nil check
devanbenz Jun 23, 2026
11946b5
feat: Add anchor validation
devanbenz Jun 24, 2026
0272a34
feat: Merge branch 'db/76/date_part' of github.com:influxdata/influxd…
devanbenz Jun 24, 2026
c368482
chore: Merge origin/master-1.x into db/76/date_part
Copilot Jun 24, 2026
d39380f
feat: Update protos, dang you copilot for borking it!
devanbenz Jun 24, 2026
474ae65
feat: Ensure that validation works after rewriting
devanbenz Jun 24, 2026
0c8ace0
feat: Support SELECT INTO, load date_part valuer only for date_part
devanbenz Jun 26, 2026
797b859
feat: fixes multi-call group bys, fills, and subquery anchor validation
devanbenz Jun 29, 2026
87d1531
feat: add datePartMap IteratorMap for date_part over subquery
devanbenz Jun 29, 2026
8866b27
feat: Implement subquery support for GROUP BY
devanbenz Jun 29, 2026
c9c76b0
feat: update sub-query semantics.
devanbenz Jun 30, 2026
803db1d
feat: Use correct ISODOW
devanbenz Jun 30, 2026
7550b40
feat: Update a few nits to have parity with how DF and PG perform dat…
devanbenz Jun 30, 2026
359ca2b
feat: Address some of Dave's review items
devanbenz Jul 1, 2026
bac9523
feat: add test export and fmt
devanbenz Jul 1, 2026
cf9917f
feat: Address daves comments, verify that we can not alloc during
devanbenz Jul 6, 2026
7c34d89
feat: Fix stray whitespace in iterator.gen.go.tmpl reduce block
devanbenz Jul 7, 2026
7059db5
feat: Address copilot findings
devanbenz Jul 8, 2026
b81488c
feat: Address davidby comments on PR
devanbenz Jul 13, 2026
67f5070
feat: Add same series tests
devanbenz Jul 13, 2026
70b4201
feat: Address a few bugs during compile time
devanbenz Jul 14, 2026
3d27aeb
feat: Address duplications in code
devanbenz Jul 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions cmd/influx/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,9 @@ func headersEqual(prev, current models.Row) bool {
if prev.Name != current.Name {
return false
}
if !reflect.DeepEqual(prev.GroupingKeys, current.GroupingKeys) {
Comment thread
devanbenz marked this conversation as resolved.
Outdated
return false
}
return tagsEqual(prev.Tags, current.Tags) && columnsEqual(prev.Columns, current.Columns)
}

Expand All @@ -890,9 +893,10 @@ func (c *CommandLine) writeCSV(response *client.Response, w io.Writer) {
suppressHeaders := len(result.Series) > 0 && headersEqual(previousHeaders, result.Series[0])
if !suppressHeaders && len(result.Series) > 0 {
previousHeaders = models.Row{
Name: result.Series[0].Name,
Tags: result.Series[0].Tags,
Columns: result.Series[0].Columns,
Name: result.Series[0].Name,
Tags: result.Series[0].Tags,
GroupingKeys: result.Series[0].GroupingKeys,
Columns: result.Series[0].Columns,
}
}

Expand Down Expand Up @@ -920,9 +924,10 @@ func (c *CommandLine) writeColumns(response *client.Response, w io.Writer) {
suppressHeaders := len(result.Series) > 0 && headersEqual(previousHeaders, result.Series[0])
if !suppressHeaders && len(result.Series) > 0 {
previousHeaders = models.Row{
Name: result.Series[0].Name,
Tags: result.Series[0].Tags,
Columns: result.Series[0].Columns,
Name: result.Series[0].Name,
Tags: result.Series[0].Tags,
GroupingKeys: result.Series[0].GroupingKeys,
Columns: result.Series[0].Columns,
}
}

Expand Down Expand Up @@ -984,6 +989,9 @@ func (c *CommandLine) formatResults(result client.Result, separator string, supp
t := fmt.Sprintf("tags: %s", (strings.Join(tags, ", ")))
rows = append(rows, t)
}
if len(row.GroupingKeys) > 0 {
rows = append(rows, fmt.Sprintf("group: %s", strings.Join(row.GroupingKeys, ", ")))
}
}

if !suppressHeaders {
Expand Down
80 changes: 75 additions & 5 deletions coordinator/statement_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ func (e *StatementExecutor) executeSelectStatement(ctx *query.ExecutionContext,
Messages: messages,
Series: []*models.Row{{
Name: "result",
Columns: []string{"time", "written"},
Columns: []string{models.TimeString, "written"},
Values: [][]interface{}{{time.Unix(0, 0).UTC(), writeN}},
}},
})
Expand Down Expand Up @@ -1394,13 +1394,31 @@ var errNoDatabaseInTarget = errors.New("no database in target")

// convertRowToPoints will convert a query result Row into Points that can be written back in.
func convertRowToPoints(measurementName string, row *models.Row, strictErrorHandling bool) ([]models.Point, error) {
// figure out which parts of the result are the time and which are the fields
// figure out which parts of the result are the time, the GROUP BY date_part
// grouping dimensions, and which are the regular fields.
//
// A GROUP BY date_part dimension (e.g. "year") is injected as an output column
// and recorded in row.GroupingKeys, but it identifies the series rather than
// carrying a value: every group shares the same representative bucket timestamp
// and the same base tag set (single window, Interval=0). Writing such a column
// as a field would collapse every group onto an identical series+timestamp
// (last-write-wins, silent data loss), so promote these columns to tags instead
// — mirroring how GROUP BY <tag> INTO writes its grouping tag.
groupingCols := make(map[string]struct{}, len(row.GroupingKeys))
Comment thread
devanbenz marked this conversation as resolved.
Outdated
for _, k := range row.GroupingKeys {
groupingCols[k] = struct{}{}
}

timeIndex := -1
fieldIndexes := make(map[string]int)
tagIndexes := make(map[string]int)
Comment thread
devanbenz marked this conversation as resolved.
Outdated
for i, c := range row.Columns {
if c == "time" {
switch {
case c == models.TimeString:
timeIndex = i
} else {
case isGroupingColumn(groupingCols, c):
tagIndexes[c] = i
default:
fieldIndexes[c] = i
}
}
Expand All @@ -1423,7 +1441,7 @@ func convertRowToPoints(measurementName string, row *models.Row, strictErrorHand
}
}

p, err := models.NewPoint(measurementName, models.NewTags(row.Tags), vals, v[timeIndex].(time.Time))
p, err := models.NewPoint(measurementName, groupingTags(row.Tags, tagIndexes, v), vals, v[timeIndex].(time.Time))
if err != nil {
if !strictErrorHandling {
// Drop points that can't be stored
Expand All @@ -1438,6 +1456,58 @@ func convertRowToPoints(measurementName string, row *models.Row, strictErrorHand
return points, nil
}

// isGroupingColumn reports whether column c is a GROUP BY date_part grouping
// dimension (and so must be written as a tag rather than a field).
func isGroupingColumn(groupingCols map[string]struct{}, c string) bool {
if len(groupingCols) == 0 {
Comment thread
devanbenz marked this conversation as resolved.
Outdated
return false
}
_, ok := groupingCols[c]
return ok
}

// groupingTags builds the tag set for a single INTO point. It starts from the
// row's base tags (shared by every group) and adds the per-group date_part
// dimension values from tagIndexes so each group becomes a distinct series. When
// there are no grouping dimensions this is equivalent to models.NewTags(base).
func groupingTags(base map[string]string, tagIndexes map[string]int, v []interface{}) models.Tags {
if len(tagIndexes) == 0 {
return models.NewTags(base)
}

merged := make(map[string]string, len(base)+len(tagIndexes))
for k, val := range base {
merged[k] = val
}
for tagName, tagIndex := range tagIndexes {
if s, ok := tagValueString(v[tagIndex]); ok {
merged[tagName] = s
}
}
return models.NewTags(merged)
}

// tagValueString renders a date_part dimension value as a tag value. date_part
// dimensions are emitted as int64 (influxql.Integer); other types are rendered
// defensively. A nil value yields ok=false so the tag is omitted rather than
// written as an empty (and therefore dropped) tag.
func tagValueString(val interface{}) (string, bool) {
switch x := val.(type) {
case nil:
return "", false
case int64:
return strconv.FormatInt(x, 10), true
case int:
return strconv.FormatInt(int64(x), 10), true
case int32:
return strconv.FormatInt(int64(x), 10), true
case string:
return x, x != ""
default:
return fmt.Sprintf("%v", x), true
}
}

// NormalizeStatement adds a default database and policy to the measurements in statement.
// Parameter defaultRetentionPolicy can be "".
func (e *StatementExecutor) NormalizeStatement(stmt influxql.Statement, defaultDatabase, defaultRetentionPolicy string) (err error) {
Expand Down
27 changes: 21 additions & 6 deletions models/rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ import (

// Row represents a single row returned from the execution of a statement.
type Row struct {
Name string `json:"name,omitempty"`
Tags map[string]string `json:"tags,omitempty"`
Columns []string `json:"columns,omitempty"`
Values [][]interface{} `json:"values,omitempty"`
Partial bool `json:"partial,omitempty"`
Name string `json:"name,omitempty"`
Tags map[string]string `json:"tags,omitempty"`
GroupingKeys []string `json:"grouping_keys,omitempty"`
Columns []string `json:"columns,omitempty"`
Values [][]interface{} `json:"values,omitempty"`
Partial bool `json:"partial,omitempty"`
}

// SameSeries returns true if r contains values for the same series as o.
func (r *Row) SameSeries(o *Row) bool {
return r.tagsHash() == o.tagsHash() && r.Name == o.Name
if len(o.GroupingKeys) == 0 && len(r.GroupingKeys) == 0 {
return r.tagsHash() == o.tagsHash() && r.Name == o.Name
}
return r.tagsHash() == o.tagsHash() && r.Name == o.Name && r.groupingKeysHash() == o.groupingKeysHash()
}

// tagsHash returns a hash of tag key/value pairs.
Expand All @@ -29,6 +33,17 @@ func (r *Row) tagsHash() uint64 {
return h.Sum64()
}

func (r *Row) groupingKeysHash() uint64 {
h := NewInlineFNV64a()
for i, k := range r.GroupingKeys {
if i > 0 {
h.Write([]byte{0})
}
h.Write([]byte(k))
}
return h.Sum64()
}

// tagKeys returns a sorted list of tag keys.
func (r *Row) tagsKeys() []string {
a := make([]string, 0, len(r.Tags))
Expand Down
6 changes: 6 additions & 0 deletions models/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ var (

// ErrTimeOutOfRange gets returned when time is out of the representable range using int64 nanoseconds since the epoch.
ErrTimeOutOfRange = fmt.Errorf("time outside range %d - %d", MinNanoTime, MaxNanoTime)

// Static objects to prevent small allocs.
TimeBytes = []byte("time")
Comment thread
devanbenz marked this conversation as resolved.

// TimeString is a variable representing the string "time"
TimeString = string(TimeBytes)
)

// SafeCalcTime safely calculates the time given. Will return error if the time is outside the
Expand Down
Loading
Loading