A column of one reference alone takes the datatype and null of the column it reads #15
@@ -627,7 +627,8 @@ tokens add cost in proportion to the output.
|
|||||||
its columns from a struct instead, because a Go caller has already written that
|
its columns from a struct instead, because a Go caller has already written that
|
||||||
schema: the fields name the columns and their types are the datatypes, so a tag
|
schema: the fields name the columns and their types are the datatypes, so a tag
|
||||||
says only what to draw, and a `datatype` in it would be a second spelling of the
|
says only what to draw, and a `datatype` in it would be a second spelling of the
|
||||||
type.
|
type. The Go type is a struct column's one datatype, so its items need not agree on
|
||||||
|
one among themselves; each must only hold that type.
|
||||||
- **A struct's records follow Go's field access, and compile on first use.** The
|
- **A struct's records follow Go's field access, and compile on first use.** The
|
||||||
fields an embedded struct promotes are the struct's own — `e.First`, as
|
fields an embedded struct promotes are the struct's own — `e.First`, as
|
||||||
`encoding/json` and SQL mappers read them — so they are columns of its record and
|
`encoding/json` and SQL mappers read them — so they are columns of its record and
|
||||||
|
|||||||
+36
-11
@@ -64,18 +64,41 @@ func datatypeOf(m map[string]any, pos position) (DataType, error) {
|
|||||||
return 0, fmt.Errorf(`datatype takes "integer", "number" or "boolean", got %q`, name)
|
return 0, fmt.Errorf(`datatype takes "integer", "number" or "boolean", got %q`, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkColumns rejects a record column whose items hold different datatypes.
|
// checkColumns rejects a record column whose items hold different datatypes, checking a column
|
||||||
func checkColumns(path string, n node) error {
|
// after the columns its items read, so a column read is named before its readers.
|
||||||
t, ok := n.(*template)
|
func checkColumns(s nodeScope) error {
|
||||||
if !ok || !t.record {
|
checked := map[node]bool{}
|
||||||
return nil
|
var check func(path, name string, column node) error
|
||||||
}
|
check = func(path, name string, column node) error {
|
||||||
for _, name := range recordColumns(t) {
|
if checked[column] {
|
||||||
if _, err := columnDatatype(t.fields[name]); err != nil {
|
return nil
|
||||||
|
}
|
||||||
|
checked[column] = true
|
||||||
|
items, _ := columnItems(column)
|
||||||
|
for _, it := range items {
|
||||||
|
if r := it.readsColumn; r != nil {
|
||||||
|
if err := check(r.a.key[1:], r.a.tail[0], r.column); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := columnDatatype(column); err != nil {
|
||||||
return fmt.Errorf("%s: field %q: %w", path, name, err)
|
return fmt.Errorf("%s: field %q: %w", path, name, err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return s(func(path string, n node) error {
|
||||||
|
t, ok := n.(*template)
|
||||||
|
if !ok || !t.record {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, name := range recordColumns(t) {
|
||||||
|
if err := check(path, name, t.fields[name]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// columnDatatype is the datatype a column's items hold. They must agree, since a
|
// columnDatatype is the datatype a column's items hold. They must agree, since a
|
||||||
@@ -136,13 +159,15 @@ func columnItems(n node) (items []*template, nullable bool) {
|
|||||||
|
|
||||||
// disagreement names the fix for two items of one column holding different datatypes.
|
// disagreement names the fix for two items of one column holding different datatypes.
|
||||||
func disagreement(a *template, da DataType, b *template, db DataType) error {
|
func disagreement(a *template, da DataType, b *template, db DataType) error {
|
||||||
bare, want := b, da
|
bare, typed, want := b, a, da
|
||||||
if da == DataTypeString {
|
if da == DataTypeString {
|
||||||
bare, want = a, db
|
bare, typed, want = a, b, db
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case da != DataTypeString && db != DataTypeString:
|
case da != DataTypeString && db != DataTypeString:
|
||||||
return fmt.Errorf("its items hold %s and %s; a column holds one datatype", da, db)
|
return fmt.Errorf("its items hold %s and %s; a column holds one datatype", da, db)
|
||||||
|
case typed.datatype == DataTypeString && (&valueProof{}).columnItem(bare).not[want] != "":
|
||||||
|
return fmt.Errorf(`item %q is not %s, the datatype item %q takes from the column it reads; to read that column as text, write {"format":"{text}","text":%q}`, bare.format, dataTypeNouns[want], typed.format, typed.format)
|
||||||
case bare.fromString: // an object may carry a weight, which this spelling would drop
|
case bare.fromString: // an object may carry a weight, which this spelling would drop
|
||||||
return fmt.Errorf(`item %q declares no datatype, and a column holds one; write it as {"format":%q,"datatype":%q}`, bare.format, bare.format, want)
|
return fmt.Errorf(`item %q declares no datatype, and a column holds one; write it as {"format":%q,"datatype":%q}`, bare.format, bare.format, want)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,14 +177,19 @@ func inlineScope(n node, label string) nodeScope {
|
|||||||
return func(fn func(path string, m node) error) error { return eachNode(n, label, fn) }
|
return func(fn func(path string, m node) error) error { return eachNode(n, label, fn) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkScope runs the per-node fences over a scope, each over the whole scope
|
// checkScope runs every fence over a scope: checkColumns, then checkRenders.
|
||||||
|
func checkScope(s nodeScope) error {
|
||||||
|
if err := checkColumns(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return checkRenders(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkRenders runs the per-node fences over a scope, each over the whole scope
|
||||||
// before the next, so which of several broken nodes is reported does not depend on
|
// before the next, so which of several broken nodes is reported does not depend on
|
||||||
// the walk. It runs after checkNoCycles, whose guarantee is what lets the walks
|
// the walk. It runs after checkNoCycles, whose guarantee is what lets the walks
|
||||||
// terminate.
|
// terminate.
|
||||||
func checkScope(s nodeScope) error {
|
func checkRenders(s nodeScope) error {
|
||||||
if err := s(checkColumns); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
mem := reachMemo{}
|
mem := reachMemo{}
|
||||||
if err := s(func(path string, n node) error { return repeatCheck(path, n, mem) }); err != nil {
|
if err := s(func(path string, n node) error { return repeatCheck(path, n, mem) }); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (f *Generator) NewTemplate(input string) (*Template, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("fejkdata: %w", err)
|
return nil, fmt.Errorf("fejkdata: %w", err)
|
||||||
}
|
}
|
||||||
if err := bindInline(n, "template", f.categories); err != nil {
|
if err := bindInline(n, "template", f.categories, checkScope); err != nil {
|
||||||
return nil, fmt.Errorf("fejkdata: %w", err)
|
return nil, fmt.Errorf("fejkdata: %w", err)
|
||||||
}
|
}
|
||||||
return &Template{g: f, n: n}, nil
|
return &Template{g: f, n: n}, nil
|
||||||
@@ -133,14 +133,14 @@ func inputValue(input string) (any, error) {
|
|||||||
return raw, nil
|
return raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindInline links an inline node's references against root and runs the fences over it,
|
// bindInline links an inline node's references against root and runs check over it, naming its
|
||||||
// naming its nodes from label.
|
// nodes from label.
|
||||||
func bindInline(n node, label string, root map[string]node) error {
|
func bindInline(n node, label string, root map[string]node, check func(nodeScope) error) error {
|
||||||
scope := inlineScope(n, label)
|
scope := inlineScope(n, label)
|
||||||
if err := linkNodeRefs(scope, root); err != nil {
|
if err := linkNodeRefs(scope, root); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return checkScope(scope)
|
return check(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
// linkNodeRefs binds the references in an inline node's templates against the
|
// linkNodeRefs binds the references in an inline node's templates against the
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ func recordOf(n node) (*template, []Column, error) {
|
|||||||
}
|
}
|
||||||
columns := make([]Column, len(names))
|
columns := make([]Column, len(names))
|
||||||
for i, name := range names {
|
for i, name := range names {
|
||||||
datatype, _ := columnDatatype(t.fields[name]) // checkColumns refused a column whose items disagree
|
datatype, _ := columnDatatype(t.fields[name]) // checkColumns refused items that disagree wherever DataType is read
|
||||||
columns[i] = Column{Name: name, DataType: datatype}
|
columns[i] = Column{Name: name, DataType: datatype}
|
||||||
}
|
}
|
||||||
return t, columns, nil
|
return t, columns, nil
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ func (s *structShape) compileRecord(root map[string]node, t reflect.Type, label
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", label, err)
|
return fmt.Errorf("%s: %w", label, err)
|
||||||
}
|
}
|
||||||
if err := bindInline(n, label, root); err != nil {
|
if err := bindInline(n, label, root, checkRenders); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
record, columns, err := recordOf(n)
|
record, columns, err := recordOf(n)
|
||||||
|
|||||||
Reference in New Issue
Block a user