semantic

package
v0.42.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 5 Imported by: 0

README

semantic

Pure logic — no I/O, no mutation of its inputs — that turns a project's model (Entity, EntityField.Mappings/NamePatterns, QueryDef.Parameters[].Meta) plus a scanned schema into the three things the core-investigation-loop feature's investigation loop needs: which column means which entity field (Resolve), what else is reachable from a selected value (RelatedLookups), and which library queries can already be run with the values on hand (Applicable). Safe to call from datatug-cli's HTTP resolver, the TUI, or a future datatug context verb — this package never talks to a database or a project store itself.

Resolve

Maps physical columns (source + collection + scanned column list) to the entity fields a project's model says they mean, per REQ:field-mapping-model: declared EntityField.Mappings win; absent a declared mapping, EntityField.NamePatterns are tried and the result is labelled inferred; a column matching neither is simply absent from the result.

entities := []*datatug.Entity{
	{
		ProjectItem: datatug.ProjectItem{ProjItemBrief: datatug.ProjItemBrief{ID: "customer"}},
		Fields: datatug.EntityFields{
			{
				ID: "id", Type: "string",
				Mappings: datatug.PhysicalRefs{
					{Source: "chinook", Collection: "Customer", Column: "CustomerId"},
				},
			},
			{
				ID: "email", Type: "string",
				NamePatterns: datatug.StringPatterns{{Type: "exact", Value: "Email"}},
			},
		},
	},
}

results := semantic.Resolve(entities, "chinook", "Customer", []semantic.Column{
	{Name: "CustomerId", Type: "int"},
	{Name: "Email", Type: "string"},
	{Name: "FirstName", Type: "string"},
})
// results == []semantic.Resolution{
//   {Column: "CustomerId", Entity: "customer", Field: "id", Provenance: semantic.Declared},
//   {Column: "Email", Entity: "customer", Field: "email", Provenance: semantic.Inferred, Rule: "namePattern:exact:Email"},
// }
// "FirstName" matches neither a declared mapping nor a name pattern, so it
// has no entry at all.

RelatedLookups

Given a selected SemanticValue (a resolved cell — build one from a Resolution plus the value the cell held), returns every other collection reachable from it: through declared foreign keys in both directions within its own source, and through mappings of the same entity field in other sources/collections, per REQ:related-lookup-model. It only says where to look — the caller (datatug-cli, per REQ:related-lookup-execution) builds and runs the actual filtered query through the access-policy path.

schema := map[semantic.SchemaKey]semantic.TableSchema{
	{Source: "chinook", Collection: "Customer"}: {
		PrimaryKey: &datatug.UniqueKey{Name: "PK_Customer", Columns: []string{"CustomerId"}},
		ReferencedBy: datatug.ReferencedBys{
			{
				DBCollectionKey: datatug.NewTableKey("Invoice", "", "", nil),
				ForeignKeys:     []*datatug.RefByForeignKey{{Name: "FK_Invoice_Customer", Columns: []string{"CustomerId"}}},
			},
		},
	},
}

selected := semantic.SemanticValue{
	Entity: "customer", Field: "id", Value: 5,
	Source: "chinook", Collection: "Customer", Column: "CustomerId",
	Provenance: semantic.Declared,
}

lookups := semantic.RelatedLookups(entities, schema, selected)
// lookups == []semantic.Lookup{
//   {Source: "chinook", Collection: "Invoice", Column: "CustomerId", Kind: semantic.LookupReferencedBy, Via: "FK_Invoice_Customer"},
//   {Source: "support-notes", Collection: "Customer", Column: "CustomerId", Kind: semantic.LookupSameField, Via: "field:customer.id"},
// }
// (the second entry assumes entities also declares a mapping of customer.id
// to source "support-notes", collection "Customer")

Applicable

Given the library's queries and the semantic values on hand (the current selection plus the Investigation Context), splits them into queries every required, Meta-tagged parameter of which can be bound, and queries still missing at least one — per REQ:applicable-queries and REQ:parameter-auto-binding. A parameter with no Meta, or an unsatisfied optional one, never blocks applicability.

query := &datatug.QueryDef{
	ProjectItem: datatug.ProjectItem{ProjItemBrief: datatug.ProjItemBrief{ID: "customer-invoices", Title: "Customer invoices"}},
	Type: datatug.QueryTypeSQL,
	Parameters: datatug.Parameters{
		{ID: "customerId", Type: "integer", IsRequired: true, Meta: &datatug.EntityFieldRef{Entity: "Customer", Field: "ID"}},
	},
}

available := []semantic.SemanticValue{
	{Entity: "Customer", Field: "ID", Value: 5, Source: "chinook", Collection: "Customer", Column: "CustomerId", Provenance: semantic.Declared},
}

applicable, notYet := semantic.Applicable([]*datatug.QueryDef{query}, available)
// applicable[0].Bindings  == []semantic.Binding{{Parameter: "customerId", Value: 5, From: available[0]}}
// applicable[0].Chain     == []string{"CustomerId → Customer.ID (declared) → parameter customerId"}
// notYet                  == nil (this query had everything it needed)

Documentation

Overview

Package semantic resolves physical columns (a source + collection + set of scanned columns) to the entity fields a project's model says they mean.

Resolve is pure - no I/O, no mutation - so it can be called from anywhere that already has a project's entities in memory: the datatug-cli HTTP resolver behind GET /datatug/semantic/columns (REQ:semantic-resolution-endpoint in datatug/datatug's core-investigation-loop feature), the TUI, or a future `datatug context` verb. Placement note: the resolver lives here in datatug-core rather than in datatug-cli/pkg/semantic (as sketched in the Phase-1 plan's task 5) because datatug-cli currently vendors its own copy of this package's types and a separate stream is restoring its module dependency; the HTTP endpoint itself is still wired in datatug-cli. This is a planner decision, not a founder ruling - open to being moved once the module dependency lands.

Declared EntityField.Mappings always win over an EntityField.NamePatterns match; NamePatterns are tried only for a column with no declared mapping anywhere in the project, and the result is labelled Inferred. See Resolve for the full tie-break rule when more than one field could claim the same column.

RelatedLookups and Applicable build on the same SemanticValue shape a Resolution plus its cell value produces: the former finds every other collection reachable from a selected value (foreign keys in both directions, plus the same field mapped elsewhere); the latter matches the project's library queries against the values on hand and reports a human-readable resolution chain for each bound parameter.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Applicable added in v0.19.0

func Applicable(queries []*datatug.QueryDef, available []SemanticValue) (applicable []ApplicableQuery, notYet []NotYetApplicable)

Applicable splits queries into those every required, Meta-tagged parameter of which can be bound from available, and those still missing at least one. A parameter with no Meta is never considered - it stays unbound and never blocks applicability, whether required or not. A Meta-tagged optional parameter (IsRequired == false) is bound when available can satisfy it, but its absence never blocks applicability either; only an unsatisfied *required* Meta-tagged parameter lands a query in notYet.

available is order-sensitive: when more than one entry shares the same Entity+Field (e.g. the Investigation Context holds a stale value and the user's current selection holds a newer one), the last entry for that Entity+Field wins - callers should append newer values after older ones.

Pure: no I/O.

Types

type ApplicableQuery added in v0.19.0

type ApplicableQuery struct {
	Query    *datatug.QueryDef
	Bindings []Binding
	// Chain is the human-readable resolution chain, one entry per bound
	// parameter: "<column> → <entity>.<field> (<declared|inferred>) →
	// parameter <parameterID>".
	Chain []string
}

ApplicableQuery is a query every required, semantically-tagged parameter of which could be bound from the available values.

type Binding added in v0.19.0

type Binding struct {
	Parameter string
	Value     interface{}
	From      SemanticValue
}

Binding is one query parameter bound to a semantic value.

type Column

type Column struct {
	Name string
	Type string
}

Column is a single physical column to resolve against a project's entities.

type Lookup added in v0.19.0

type Lookup struct {
	Source     string
	Collection string
	Column     string
	Kind       LookupKind
	Via        string
}

Lookup is one related-record path reachable from a SemanticValue: the caller filters Lookup.Collection (in Lookup.Source) where Lookup.Column equals the selected value - RelatedLookups itself performs no such filter, it only says where to look (REQ:related-lookup-execution builds and runs that query server-side).

func RelatedLookups added in v0.19.0

func RelatedLookups(entities []*datatug.Entity, schemaByCollection map[SchemaKey]TableSchema, selected SemanticValue) []Lookup

RelatedLookups returns every other collection reachable from a selected semantic value: through declared foreign keys in both directions within selected's own source (schemaByCollection must carry that source's scanned schema for this to find anything), and through mappings of the same entity field (selected.Entity + selected.Field) in other sources or collections. A column that is neither part of a foreign-key relationship nor mapped anywhere else yields nothing.

The scanned-schema model does not record which column of a referenced table an outgoing foreign key targets, only the referencing table's own columns (datatug.ForeignKey has no "referenced column" field). Two heuristics fill that gap, both using the referenced table's primary key as the assumed join target - the common case, and the only information this model actually carries:

  • LookupForeignKey: selected.Column matches one of the current table's own ForeignKey.Columns: this reports RefTable's primary key (its first column, when the schema for RefTable is known and single-column-keyed; the selected column's own name otherwise) as the join column.
  • LookupReferencedBy: selected.Column must be part of the current table's own primary key for a "referenced by" lookup to fire at all, since that is this model's only signal that an incoming foreign key targets this exact column.

Order is deterministic: lookups in selected's own source come first, ordered by target Collection name (ties broken by Kind); then cross-source SameField lookups, ordered by Source then Collection name. Pure: no I/O.

type LookupKind added in v0.19.0

type LookupKind string

LookupKind identifies how a Lookup was derived.

const (
	// LookupForeignKey means the selected value's own column is a foreign
	// key, pointing at Lookup.Collection.
	LookupForeignKey LookupKind = "foreignKey"
	// LookupReferencedBy means Lookup.Collection has a foreign key pointing
	// back at the selected value's table.
	LookupReferencedBy LookupKind = "referencedBy"
	// LookupSameField means Lookup.Collection maps the same semantic field
	// (the selected value's Entity + Field) in a different source or
	// collection.
	LookupSameField LookupKind = "sameField"
)

type NotYetApplicable added in v0.19.0

type NotYetApplicable struct {
	Query *datatug.QueryDef
	// Missing lists each unsatisfied required parameter's semantic field, as
	// "<entity>.<field>".
	Missing []string
}

NotYetApplicable is a query with at least one required, semantically-tagged parameter the available values could not satisfy.

type Provenance

type Provenance string

Provenance identifies how a Resolution was determined.

const (
	// Declared means the column matched an EntityField's declared Mappings.
	Declared Provenance = "declared"
	// Inferred means the column matched an EntityField's NamePatterns, with
	// no declared mapping present for it.
	Inferred Provenance = "inferred"
)

type Resolution

type Resolution struct {
	Column     string
	Entity     string
	Field      string
	Provenance Provenance
	// Rule documents how the winner was chosen: which name pattern matched
	// (for Inferred), and/or a tie-break note when more than one field
	// resolved the same column at the same provenance level.
	Rule string
	// Err is set only when the column had NamePatterns candidates that could
	// not be evaluated (e.g. an invalid regexp) and neither a declared
	// mapping nor a valid inferred candidate resolved it. When Err is set,
	// Entity, Field, Provenance and Rule are zero.
	Err error
}

Resolution is what one Column resolved to.

func Resolve

func Resolve(entities []*datatug.Entity, source, collection string, columns []Column) []Resolution

Resolve returns, for each Column, the entity field it maps to in the given source/collection (table): declared EntityField.Mappings win; absent a declared mapping, EntityField.NamePatterns are tried; a column matching neither is simply absent from the result (see REQ:field-mapping-model in datatug/datatug's core-investigation-loop feature).

When more than one field resolves the same column at the same provenance level, the field belonging to the lowest Entity.ID wins (Entity.ID, then EntityField.ID, ascending) and the tie is recorded in the winning Resolution's Rule. This keeps Resolve deterministic without requiring callers to pre-sort entities.

Resolve is pure: it performs no I/O and does not mutate entities. It never panics - a pattern that fails to evaluate (e.g. invalid regexp syntax) yields a Resolution with Err set instead, unless a declared mapping or another, valid pattern already resolved the column.

type SchemaKey added in v0.19.0

type SchemaKey struct {
	Source     string
	Collection string
}

SchemaKey identifies one collection (table or view) within one source.

type SemanticValue added in v0.19.0

type SemanticValue struct {
	Entity     string
	Field      string
	Value      interface{}
	Source     string
	Collection string
	Column     string
	Provenance Provenance
}

SemanticValue is one resolved cell: a value in a physical column, and the semantic field a resolver (see Resolve) said it means. Provenance is a deliberate addition beyond the field-mapping-model REQ's own sketch of this type: it is needed to render the "(declared)"/"(inferred)" segment of an Applicable resolution Chain (REQ:applicable-queries), so a caller can build one SemanticValue directly from a Resolution and pass it to both RelatedLookups and Applicable.

type TableSchema added in v0.19.0

type TableSchema struct {
	PrimaryKey   *datatug.UniqueKey
	ForeignKeys  datatug.ForeignKeys
	ReferencedBy datatug.ReferencedBys
}

TableSchema is the subset of a scanned table/view's metadata RelatedLookups needs: its primary key, and its foreign-key graph in both directions (datatug.ForeignKeys/datatug.ReferencedBys, as scanned by the DB driver).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL