Files
2026-07-13 12:32:25 +08:00

261 lines
9.3 KiB
Go

// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package id
import (
"bytes"
"fmt"
"strings"
"unsafe"
)
// Id uses one of two formats. Which format is being used is marked by the upper Section bit being either 0 or 1.
// Often, an ID contains information that will commonly be accessed by the item, so the first format is tailored for
// efficient retrieval of specific segments. If an item is larger than the size limit (255, size is stored as an uint8),
// then we use the second format, which inserts a separator between items. This allows Id to hold any data in case
// the need arises in the future, but in practice we'll only see the first format (since data will usually be
// identifiers or smaller embedded IDs). Id IDs will be accessed far more often than they'll be created, hence the
// focus on efficient retrieval rather than simplicity of storage.
//
// First format (upper bit is 0):
// The first byte is the section
// The second byte contains the number of segments N (up to 255 segments)
// The next N bytes contain the length of each respective segment (up to 255 bytes)
// The remaining bytes are the original string data, stored contiguously
// Second format (upper bit is 1):
// The first byte is the section
// The remaining bytes are the original string data, stored with the separator between each segment
const (
// idSeparator marks the different data sections in an Id. This is the null byte since that byte is invalid in
// all identifiers, so we can guarantee that it's safe to use as a separator. This is used when an individual data
// segment is larger than 254 bytes.
idSeparator = "\x00"
// formatMask is the upper bit that determines whether we're using the first or second format.
formatMask = uint8(0x80)
// Null is an empty, invalid ID.
Null Id = ""
// NullAccessMethod is an empty, invalid ID. This is exactly equivalent to Null.
NullAccessMethod AccessMethod = ""
// NullCast is an empty, invalid ID. This is exactly equivalent to Null.
NullCast Cast = ""
// NullCheck is an empty, invalid ID. This is exactly equivalent to Null.
NullCheck Check = ""
// NullCollation is an empty, invalid ID. This is exactly equivalent to Null.
NullCollation Collation = ""
// NullColumnDefault is an empty, invalid ID. This is exactly equivalent to Null.
NullColumnDefault ColumnDefault = ""
// NullDatabase is an empty, invalid ID. This is exactly equivalent to Null.
NullDatabase Database = ""
// NullEnumLabel is an empty, invalid ID. This is exactly equivalent to Null.
NullEnumLabel EnumLabel = ""
// NullExtension is an empty, invalid ID. This is exactly equivalent to Null.
NullExtension Extension = ""
// NullForeignKey is an empty, invalid ID. This is exactly equivalent to Null.
NullForeignKey ForeignKey = ""
// NullFunction is an empty, invalid ID. This is exactly equivalent to Null.
NullFunction Function = ""
// NullIndex is an empty, invalid ID. This is exactly equivalent to Null.
NullIndex Index = ""
// NullNamespace is an empty, invalid ID. This is exactly equivalent to Null.
NullNamespace Namespace = ""
// NullProcedure is an empty, invalid ID. This is exactly equivalent to Null.
NullProcedure Procedure = ""
// NullSequence is an empty, invalid ID. This is exactly equivalent to Null.
NullSequence Sequence = ""
// NullTable is an empty, invalid ID. This is exactly equivalent to Null.
NullTable Table = ""
// NullTrigger is an empty, invalid ID. This is exactly equivalent to Null.
NullTrigger Trigger = ""
// NullType is an empty, invalid ID. This is exactly equivalent to Null.
NullType Type = ""
// NullView is an empty, invalid ID. This is exactly equivalent to Null.
NullView View = ""
)
// Id is an ID that is used within Doltgres. This ID is never exposed to clients through any normal means, and
// exists solely for internal operations to be able to identify specific items. This functions as an internal
// replacement for Postgres' OIDs.
type Id string
// NewId constructs an Id using the given section and data. In general, you should prefer to use the `NewIDTYPE` that
// matches the Section that's being created, and then convert that to an Id for returning or storage. You almost never
// want to call this function directly.
func NewId(section Section, data ...string) Id {
if section == Section_Null {
// It's easier if there's only one canonical way to represent a null ID, so we'll return our constant instead of
// creating a new string
return Null
}
if len(data) > 255 {
return newIdSecondFormat(section, data)
}
buf := bytes.Buffer{}
buf.WriteByte(uint8(section))
buf.WriteByte(uint8(len(data)))
for _, segment := range data {
segmentLength := len(segment)
if segmentLength > 255 {
return newIdSecondFormat(section, data)
}
buf.WriteByte(uint8(segmentLength))
}
for _, segment := range data {
buf.WriteString(segment)
}
return Id(buf.Bytes())
}
// newIdSecondFormat constructs an Id using the given section and data. This always returns the second format (using the
// separator).
func newIdSecondFormat(section Section, data []string) Id {
buf := bytes.Buffer{}
buf.WriteByte(uint8(section) | formatMask)
for i, segment := range data {
if i > 0 {
buf.WriteString(idSeparator)
}
buf.WriteString(segment)
}
return Id(buf.Bytes())
}
// IsValid returns whether the Id is valid.
func (id Id) IsValid() bool {
// We don't allow setting the section to Section_Null, so we can do a simple length check
return len(id) > 0
}
// Section returns the Section for this Id.
func (id Id) Section() Section {
if len(id) == 0 {
return Section_Null
}
return Section(id[0] & (^formatMask))
}
// Data returns the original data used to create this Id.
func (id Id) Data() []string {
if len(id) <= 1 {
return nil
}
if id[0]&formatMask == formatMask {
// Second format
return strings.Split(string(id[1:]), idSeparator)
} else {
// First format
segmentCount := int(id[1])
data := id[2+segmentCount:] // We skip 2 for the section and count bytes, then the number of segment counts
segments := make([]string, segmentCount)
start := 0
for i := 0; i < segmentCount; i++ {
length := int(id[2+i])
segments[i] = string(data[start : start+length])
start += length
}
return segments
}
}
// SegmentCount returns the number of segments that were in the original data.
func (id Id) SegmentCount() int {
if len(id) <= 1 {
return 0
}
if id[0]&formatMask == formatMask {
// Second format
return len(id.Data())
} else {
// First format
return int(id[1])
}
}
// Segment returns the segment from the given index. An empty string is returned for an index not contained by the ID.
func (id Id) Segment(index int) string {
if index < 0 || len(id) <= 1 {
return ""
}
if id[0]&formatMask == formatMask {
// Second format
data := id.Data()
if index >= len(data) {
return ""
}
return data[index]
} else {
// First format
segmentCount := int(id[1])
data := id[2+segmentCount:] // We skip 2 for the section and count bytes, then the number of segment counts
if index >= segmentCount {
return ""
}
start := 0
currentLength := 0
for i := 0; i <= index; i++ {
start += currentLength
currentLength = int(id[2+i])
}
return string(data[start : start+currentLength])
}
}
// String returns a display-suitable version of the ID. Although the ID is implemented as a string, it should not be
// treated as a string except for the purposes of storage and retrieval.
func (id Id) String() string {
data := id.Data()
if len(data) == 0 {
return fmt.Sprintf(`{%s:[]}`, id.Section().String())
}
return fmt.Sprintf(`{%s:["%s"]}`, id.Section().String(), strings.Join(data, `","`))
}
// CaseString returns a quoted string that may be used to represent this ID in a switch-case.
func (id Id) CaseString() string {
if len(id) == 0 {
return `""`
}
if id[0]&formatMask == formatMask {
// Second format
data := strings.ReplaceAll(string(id[1:]), "\x00", `\x00`)
data = strings.ReplaceAll(data, `"`, `\x22`)
return fmt.Sprintf(`"\x%02x%s"`, id[0], data)
} else {
// First format
sb := strings.Builder{}
sb.Grow(len(id) + 32)
sb.WriteRune('"')
count := int(id[1])
sb.WriteString(fmt.Sprintf(`\x%02x\x%02x`, id[0], count))
for i := 0; i < count; i++ {
sb.WriteString(fmt.Sprintf(`\x%02x`, id[2+i]))
}
sb.WriteString(strings.ReplaceAll(string(id[2+count:]), `"`, `\x22`))
sb.WriteRune('"')
return sb.String()
}
}
// UnderlyingBytes returns the underlying bytes for the ID. These must not be modified, as this is intended solely for
// efficient usage of operations that require byte slices.
func (id Id) UnderlyingBytes() []byte {
return unsafe.Slice(unsafe.StringData(string(id)), len(id))
}
// usesSecondFormat returns whether the separator is used, which is the second format.
func (id Id) usesSecondFormat() bool {
return len(id) > 0 && id[0]&formatMask == formatMask
}