Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
59 changes: 48 additions & 11 deletions v1/ast/interning.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,8 @@ var (
minusOneValue Value = Number("-1")
minusOneTerm = NewTerm(minusOneValue)

internedStringTerms = map[string]*Term{
"": InternedEmptyString,
}
internedStringValues = map[string]Value{"": InternedEmptyStringValue}
internedStringTerms = map[string]*Term{"": InternedEmptyString}

internedVarValues = map[string]Value{
"input": Var("input"),
Expand All @@ -68,11 +67,12 @@ var (
// interned terms are shared globally, and the underlying map is not thread-safe.
func InternStringTerm(str ...string) {
for _, s := range str {
if _, ok := internedStringTerms[s]; ok {
if _, ok := internedStringValues[s]; ok {
continue
}

internedStringTerms[s] = &Term{Value: String(s)}
internedStringValues[s] = String(s)
internedStringTerms[s] = &Term{Value: internedStringValues[s]}
}
}

Expand Down Expand Up @@ -125,7 +125,7 @@ func HasInternedValue[T internable](v T) bool {
// InternedValue returns an interned Value for scalar v, if the value is
// interned. If the value is not interned, a new Value is returned.
func InternedValue[T internable](v T) Value {
return InternedValueOr(v, internedTermValue)
return InternedValueOr(v, newValue)
}

// InternedVarValue returns an interned Var Value for the given name. If the
Expand All @@ -144,6 +144,8 @@ func InternedValueOr[T internable](v T, supplier func(T) Value) Value {
switch value := any(v).(type) {
case bool:
return internedBooleanValue(value)
case string:
return internedStringValue(value)
case int:
return internedIntNumberValue(value)
case int8:
Expand All @@ -168,6 +170,37 @@ func InternedValueOr[T internable](v T, supplier func(T) Value) Value {
return supplier(v)
}

func newValue[T internable](v T) Value {
switch value := any(v).(type) {
case bool:
return Boolean(value)
case string:
return String(value)
case int:
return Number(strconv.Itoa(value))
case int8:
return Number(strconv.Itoa(int(value)))
case int16:
return Number(strconv.Itoa(int(value)))
case int32:
return Number(strconv.Itoa(int(value)))
case int64:
return Number(strconv.Itoa(int(value)))
case uint:
return Number(strconv.Itoa(int(value)))
case uint8:
return Number(strconv.Itoa(int(value)))
case uint16:
return Number(strconv.Itoa(int(value)))
case uint32:
return Number(strconv.Itoa(int(value)))
case uint64:
return Number(strconv.Itoa(int(value)))
default:
panic("unreachable")
}
}

// Interned returns a possibly interned term for the given scalar value.
// If the value is not interned, a new term is created for that value.
func InternedTerm[T internable](v T) *Term {
Expand Down Expand Up @@ -267,6 +300,14 @@ func internedBooleanValue(b bool) Value {
return InternedBooleanFalseValue
}

func internedStringValue(s string) Value {
if v, ok := internedStringValues[s]; ok {
return v
}

return String(s)
}

// InternedBooleanTerm returns an interned term with the given boolean value.
func internedBooleanTerm(b bool) *Term {
if b {
Expand Down Expand Up @@ -300,7 +341,7 @@ func internedIntNumberTerm(i int) *Term {
return minusOneTerm
}

return &Term{Value: Number(strconv.Itoa(i))}
return &Term{Value: internedIntNumberValue(i)}
}

// InternedStringTerm returns an interned term with the given string value. If the
Expand All @@ -314,10 +355,6 @@ func internedStringTerm(s string) *Term {
return StringTerm(s)
}

func internedTermValue[T internable](v T) Value {
return InternedTerm(v).Value
}

func init() {
InternStringTerm(
// Numbers
Expand Down
15 changes: 15 additions & 0 deletions v1/storage/inmem/inmem.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,21 @@ func NewFromReaderWithOpts(r io.Reader, opts ...Opt) storage.Store {
return NewFromObjectWithOpts(data, opts...)
}

// NewFromASTObject returns a new in-memory store from the supplied AST object, with
// [OptReturnASTValuesOnRead] enabled. This allows avoiding the overhead of an extra AST
// -> map[string]any -> AST round trip for callers whose data already exists in AST form.
// Note that data passed is **not** copied and it is the responsibility of the caller to
// ensure either that ownership of the data is transferred fully to the store, or when
// that's not possible, that a deep copy of the original data is passed.
func NewFromASTObject(data ast.Object) storage.Store {
return &store{
data: data,
triggers: map[*handle]storage.TriggerConfig{},
policies: map[string][]byte{},
returnASTValuesOnRead: true,
}
}

type store struct {
rmu sync.RWMutex // reader-writer lock
wmu sync.Mutex // writer lock
Expand Down
13 changes: 13 additions & 0 deletions v1/storage/inmem/inmem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1543,6 +1543,19 @@ func TestInMemoryMakeDirAST(t *testing.T) {
}
}

func TestNewFromASTObject(t *testing.T) {
store := NewFromASTObject(ast.NewObject(ast.Item(ast.InternedTerm("key"), ast.InternedTerm("value"))))

value, err := storage.ReadOne(t.Context(), store, storage.MustParsePath("/key"))
if err != nil {
t.Fatal(err)
}

if v, ok := value.(ast.Value); !ok || ast.Compare(v, ast.InternedTerm("value")) != 0 {
t.Fatalf("expected ast.InternedTerm(\"value\") but got %T: %v", value, value)
}
}

func loadExpectedResult(input string) any {
if len(input) == 0 {
return nil
Expand Down
Loading