Skip to content

Commit cafcd8f

Browse files
Map a parameter type by its canonical spelling and stop on an unknown one
tools/codegen reads each parameter's canonical spelling from the MEOS-API catalog, the C spelling with every typedef the catalog states resolved, and fills the one further parameter of a math operation by that spelling: argConversions fills a double, an int and a bool, so a typedef the catalog resolves to one of them is filled as that type. argUnfillable states the two types the one value of a query cannot fill, const Interval * and interpType, each with its reason, and an operation taking one keeps its row in mathOps and gets no case in the cgo dispatch. A parameter whose canonical spelling is absent, or names a type in neither map, stops the generator with an error naming the function and the type, so a type the catalog gives a parameter reaches the tier as a failing check rather than as an operation missing from the dispatch. Measured. Over the catalog of MobilityDB master the generator selects 21 math operations and serves 18, the operations and cases of master's tables, whose generated dispatch differs only in the header comment naming the two unfillable types; the three without a case are the two taking an interval and the one taking an interpolation. Witness. tools/codegen/main_test.go builds catalog entries and asserts that an int32 parameter whose canonical spelling is int is filled as an int, that an interval parameter keeps its operation out of the dispatch, and that a parameter of an unknown type, or one without a canonical spelling, stops the generator naming the function. GENERATION.md states the rule.
1 parent fc57fb3 commit cafcd8f

4 files changed

Lines changed: 144 additions & 20 deletions

File tree

‎GENERATION.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ MEOS C symbol behind it, and the C type of its one further parameter.
1616

1717
`mathops_meos_gen.go` is the cgo call for each of those operations, behind the
1818
`meos` build tag. It is what keeps a C symbol out of hand-written code: a MEOS
19-
rename becomes a compile error rather than a wrong call.
19+
rename becomes a compile error rather than a wrong call. The further parameter
20+
is filled from the one value a query carries according to the canonical
21+
spelling of its type, the C spelling with every typedef the catalog states
22+
resolved: `argConversions` in `tools/codegen` fills a `double`, an `int` and a
23+
`bool`, and `argUnfillable` states the types one value cannot fill, an interval
24+
and an interpolation, whose operations get no call. A type in neither stops the
25+
generator, naming the function that takes it.
2026

2127
Run it against a catalog:
2228

‎mathops_meos_gen.go‎

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tools/codegen/main.go‎

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,13 @@ type function struct {
4949
Returns ctype `json:"returnType"`
5050
}
5151

52+
// param is one parameter of a catalog function: its C spelling as the header
53+
// writes it, and its canonical spelling, which is the C spelling with every
54+
// typedef the catalog states resolved.
5255
type param struct {
53-
Name string `json:"name"`
54-
CType string `json:"cType"`
56+
Name string `json:"name"`
57+
CType string `json:"cType"`
58+
Canonical string `json:"canonical"`
5559
}
5660

5761
type ctype struct {
@@ -89,7 +93,10 @@ func main() {
8993
"MEOS-API that states one, or it is not a MEOS-API catalog", *path))
9094
}
9195

92-
ops := mathOperations(c.Functions)
96+
ops, err := mathOperations(c.Functions)
97+
if err != nil {
98+
fail(fmt.Errorf("%s: %w", *path, err))
99+
}
93100
if len(ops) == 0 {
94101
fail(fmt.Errorf("%s names no temporal math operation: the groups this reads, "+
95102
"meos_temporal_math and meos_temporal_transf, are not where they were", *path))
@@ -139,9 +146,10 @@ var mathPrefixes = map[string]bool{
139146
// mathOperation is one MEOS function over a temporal number that the tier can
140147
// apply to a stream record.
141148
type mathOperation struct {
142-
SQLName string // the SQL name, which is the same on every engine
143-
Symbol string // the MEOS C symbol
144-
Arg string // the C type of its one further parameter, empty when it takes none
149+
SQLName string // the SQL name, which is the same on every engine
150+
Symbol string // the MEOS C symbol
151+
Arg string // the C type of its one further parameter, empty when it takes none
152+
ArgCanonical string // the canonical spelling of that type, empty when it takes none
145153
}
146154

147155
// mathOperations selects, from the catalog, the functions that take a temporal
@@ -157,7 +165,13 @@ type mathOperation struct {
157165
// arithmetic form <op>_tfloat_float;
158166
// - it takes at most one further parameter, and that parameter is not itself
159167
// a Temporal, since a stream record carries one value and not two.
160-
func mathOperations(fns []function) []mathOperation {
168+
//
169+
// The type of that further parameter is read by its canonical spelling, and it
170+
// must be one argConversions fills or one argUnfillable states a query value
171+
// cannot fill: any other type is an error, so a type the catalog gives a
172+
// parameter reaches this tier as a failing generator rather than as a missing
173+
// operation.
174+
func mathOperations(fns []function) ([]mathOperation, error) {
161175
var out []mathOperation
162176
for _, f := range fns {
163177
if !mathGroups[f.Group] || f.Category != "transformation" || f.SQLName == "" {
@@ -176,32 +190,53 @@ func mathOperations(fns []function) []mathOperation {
176190
if !mathPrefixes[strings.SplitN(f.Name, "_", 2)[0]] && !strings.HasSuffix(f.Name, "_tfloat_float") {
177191
continue
178192
}
179-
arg := ""
193+
op := mathOperation{SQLName: f.SQLName, Symbol: f.Name}
180194
if len(rest) == 1 {
181-
arg = rest[0].CType
195+
op.Arg = rest[0].CType
196+
op.ArgCanonical = rest[0].Canonical
197+
if op.ArgCanonical == "" {
198+
return nil, fmt.Errorf("%s: the parameter %s carries no canonical spelling, "+
199+
"so its type cannot be read from the catalog", f.Name, rest[0].Name)
200+
}
201+
if _, ok := argConversions[op.ArgCanonical]; !ok {
202+
if _, ok := argUnfillable[op.ArgCanonical]; !ok {
203+
return nil, fmt.Errorf("%s: the parameter %s has the type %q, which is "+
204+
"neither filled from a query value (argConversions) nor stated as one "+
205+
"a query value cannot fill (argUnfillable)", f.Name, rest[0].Name, op.ArgCanonical)
206+
}
207+
}
182208
}
183-
out = append(out, mathOperation{SQLName: f.SQLName, Symbol: f.Name, Arg: arg})
209+
out = append(out, op)
184210
}
185211
sort.Slice(out, func(i, j int) bool {
186212
if out[i].SQLName != out[j].SQLName {
187213
return out[i].SQLName < out[j].SQLName
188214
}
189215
return out[i].Symbol < out[j].Symbol
190216
})
191-
return out
217+
return out, nil
192218
}
193219

194220
// argConversions turn the one float64 a continuous query carries into the C
195-
// value a MEOS function's further parameter takes. A parameter type absent from
196-
// this map cannot be filled from a query, so the operation carrying it gets no
197-
// case and reaches the caller as one the dispatch does not serve.
221+
// value a MEOS function's further parameter takes, keyed by the canonical
222+
// spelling of the parameter's type, so a typedef the catalog resolves to one of
223+
// these types is filled as that type.
198224
var argConversions = map[string]string{
199225
"": "",
200226
"double": "C.double(arg)",
201227
"int": "C.int(arg)",
202228
"bool": "arg != 0",
203229
}
204230

231+
// argUnfillable are the canonical parameter types the one float64 of a query
232+
// cannot fill, each with the reason. An operation taking one of them gets no
233+
// case and reaches the caller as one the dispatch does not serve. A type in
234+
// neither this map nor argConversions stops the generator.
235+
var argUnfillable = map[string]string{
236+
"const Interval *": "an interval is a duration, not a single number",
237+
"interpType": "an interpolation is an enumeration, not a number",
238+
}
239+
205240
// renderDispatch writes the cgo call for each operation, which is what keeps
206241
// the C symbol behind an operation out of hand-written code: the symbol comes
207242
// from the catalog, and a MEOS rename becomes a compile error here rather than
@@ -217,8 +252,9 @@ func renderDispatch(ops []mathOperation) ([]byte, int, error) {
217252
// go run ./tools/codegen -catalog <meos-idl.json> -o-meos mathops_meos_gen.go
218253
//
219254
// One case per operation the catalog states over a temporal number, calling the
220-
// MEOS symbol the catalog names. An operation whose further parameter cannot be
221-
// filled from the single value a query carries gets no case.
255+
// MEOS symbol the catalog names. An operation whose further parameter has a type
256+
// the single value a query carries cannot fill, an interval or an
257+
// interpolation, gets no case.
222258
223259
package main
224260
@@ -237,7 +273,7 @@ func callMathOp(sqlName string, temp *C.Temporal, arg float64) (*C.Temporal, boo
237273
`)
238274
called := 0
239275
for _, op := range ops {
240-
conv, ok := argConversions[op.Arg]
276+
conv, ok := argConversions[op.ArgCanonical]
241277
if !ok {
242278
continue
243279
}

‎tools/codegen/main_test.go‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package main
2+
3+
// These test the generator's reading of parameter types by calling
4+
// #mathOperations and #renderDispatch directly, beside the checks of the
5+
// generated table in the tier's own catalog_test.go
6+
// (#TestTheCatalogStatesMoreThanTheTierLifts), which read its output.
7+
8+
import (
9+
"strings"
10+
"testing"
11+
)
12+
13+
// mathFunction is a catalog entry that passes every selection rule of
14+
// #mathOperations, with one further parameter of the given C and canonical
15+
// spellings.
16+
func mathFunction(name, cType, canonical string) function {
17+
return function{
18+
Name: name,
19+
Group: "meos_temporal_math",
20+
Category: "transformation",
21+
SQLName: name,
22+
Params: []param{
23+
{Name: "temp", CType: "const Temporal *", Canonical: "const Temporal *"},
24+
{Name: "arg", CType: cType, Canonical: canonical},
25+
},
26+
Returns: ctype{C: "Temporal *"},
27+
}
28+
}
29+
30+
// A parameter whose C spelling is a typedef is filled by the type the catalog
31+
// resolves it to, so #renderDispatch serves it.
32+
func TestAParameterIsFilledByItsCanonicalType(t *testing.T) {
33+
ops, err := mathOperations([]function{mathFunction("tfloat_scale", "int32", "int")})
34+
if err != nil {
35+
t.Fatal(err)
36+
}
37+
if len(ops) != 1 || ops[0].Arg != "int32" || ops[0].ArgCanonical != "int" {
38+
t.Fatalf("got %+v, want one operation with Arg int32 and ArgCanonical int", ops)
39+
}
40+
src, called, err := renderDispatch(ops)
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
if called != 1 || !strings.Contains(string(src), "C.tfloat_scale(temp, C.int(arg))") {
45+
t.Fatalf("the dispatch serves %d operations, want the int32 parameter filled as an int:\n%s", called, src)
46+
}
47+
}
48+
49+
// A parameter of a type a query value cannot fill keeps its operation in the
50+
// table and out of the dispatch #renderDispatch writes.
51+
func TestAnUnfillableParameterGetsNoCase(t *testing.T) {
52+
ops, err := mathOperations([]function{mathFunction("tfloat_shift", "const Interval *", "const Interval *")})
53+
if err != nil {
54+
t.Fatal(err)
55+
}
56+
if len(ops) != 1 {
57+
t.Fatalf("got %d operations, want 1", len(ops))
58+
}
59+
src, called, err := renderDispatch(ops)
60+
if err != nil {
61+
t.Fatal(err)
62+
}
63+
if called != 0 || strings.Contains(string(src), "tfloat_shift") {
64+
t.Fatalf("the dispatch serves %d operations, want none:\n%s", called, src)
65+
}
66+
}
67+
68+
// A parameter type #mathOperations does not know, or one the catalog gives no
69+
// canonical spelling, stops the generator and names the function.
70+
func TestAnUnknownParameterTypeStopsTheGenerator(t *testing.T) {
71+
for _, tc := range []struct{ cType, canonical, want string }{
72+
{"const Span *", "const Span *", `"const Span *"`},
73+
{"int32", "", "no canonical spelling"},
74+
} {
75+
_, err := mathOperations([]function{mathFunction("tfloat_odd", tc.cType, tc.canonical)})
76+
if err == nil || !strings.Contains(err.Error(), "tfloat_odd") || !strings.Contains(err.Error(), tc.want) {
77+
t.Errorf("parameter %q canonical %q: got error %v, want one naming tfloat_odd and %s",
78+
tc.cType, tc.canonical, err, tc.want)
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)