feat: CDX compound index write + {||} parsing + zero known constraints

All 3 remaining known constraints resolved. CLAUDE.md now shows zero.

1. CDX compound index WRITE support (was read-only)

   New file: hbrdd/cdx/build.go (~400 LOC)
   - CreateOrAddTag() builds Harbour-compatible CDX files
   - Bit-packed leaf pages (RecBits/DupBits/TrlBits compression)
   - Interior nodes with big-endian RecNo/ChildPage
   - Compound root directory (structural B-tree of tag names)
   - Append-safe: preserves existing tags when adding new ones
   - Linked leaf pages (LeftPtr/RightPtr for sequential scan)

   Pipeline: INDEX ON expr TAG tagname TO file
   - ast.IndexCmd gains TagName field
   - Parser captures TAG name (was discarded)
   - gengo passes TagName to OrderCreateParams
   - indexer.go routes to cdx.CreateOrAddTag when TAG specified

   Verified: 3 tags (BYNAME/BYCITY/BYAGE), OrdSetFocus by name,
   SEEK, GoTop/GoBottom, close+reopen with SET INDEX TO

2. {||} empty code block parsing in function arguments

   Parser's parseArrayOrBlock() called parseExpr() unconditionally
   after closing |, failing when body was empty ({||}).
   Fix: check for RBRACE after closing | and emit NIL literal body.
   {=>} empty hash already worked.

3. Semicolon IF...ENDIF — already worked (removed from constraints)

Tests:
  go test ./...        14 packages ALL PASS
  FiveSql2             43/43 100%
  compat_harbour       51/51

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 22:58:09 +09:00
parent e5d27951fd
commit 4d5621c21a
8 changed files with 590 additions and 22 deletions

View File

@@ -65,16 +65,15 @@ cd ~/tmp && rm -f *.dbf __cte_*.dbf && /tmp/test_sql
## 알려진 제약사항
| 항목 | 상태 | 우회 방법 |
|------|------|----------|
| 세미콜론 IF...ENDIF | Five 파서 미지원 | 여러 줄로 분리 |
| `{||}` / `{=>}` 함수 인자 내 | 파서가 빈 블록/해시를 인자 위치에서 파싱 실패 | 변수에 담아서 전달 |
| CDX compound index 쓰기 | 읽기만 지원, 생성 불가 | NTX 사용 또는 Harbour로 CDX 생성 |
현재 알려진 제약사항 없음. 모든 이전 제약이 해결됨.
### 해결된 제약 (2026-04-11~13)
| 항목 | 커밋 |
|------|------|
| 세미콜론 IF...ENDIF | 이미 동작 확인 (2026-04-13) |
| `{||}` / `{=>}` 함수 인자 파싱 실패 | 빈 블록 body에 NIL 리터럴 emit |
| CDX compound index 쓰기 미지원 | CDX 빌더 구현 (비트팩 리프+compound root) |
| STATIC inside FUNCTION → panic | `5bfdc47` — Go 패키지 변수로 emit |
| FIELD->NAME 빈 값 반환 | `e95afad` — GetAliasField 반환 타입 수정 |
| OrdSetFocus(n) 무동작 | `e95afad` — 숫자→문자열 변환 수정 |

View File

@@ -855,12 +855,13 @@ func (s *DeleteCmd) Pos() token.Position { return s.DeletePos }
func (s *DeleteCmd) End() token.Position { return s.DeletePos }
func (s *DeleteCmd) stmtNode() {}
// IndexCmd represents INDEX ON expr TO file [FOR cond] [UNIQUE] [DESCENDING]
// IndexCmd represents INDEX ON expr [TAG tagname] TO file [FOR cond] [UNIQUE] [DESCENDING]
type IndexCmd struct {
IndexPos token.Position
KeyExpr Expr
File Expr
ForCond Expr // nil if no FOR
TagName string // TAG name for CDX compound index (empty = NTX)
Unique bool
Descending bool
}

View File

@@ -626,8 +626,8 @@ func (g *Generator) emitStmt(stmt ast.Stmt, locals localMap) {
}
// Set VM callback for UDF evaluation during index build
g.writeln("dbf.KeyEvalFunc = func(expr string) hbrt.Value { return t.MacroEval(expr) }")
g.writeln(fmt.Sprintf("idx.OrderCreate(hbrdd.OrderCreateParams{KeyExpr: _keyExpr, FilePath: _file, ForExpr: %s, Unique: %v, Descending: %v})",
forExpr, s.Unique, s.Descending))
g.writeln(fmt.Sprintf("idx.OrderCreate(hbrdd.OrderCreateParams{KeyExpr: _keyExpr, FilePath: _file, ForExpr: %s, TagName: %q, Unique: %v, Descending: %v})",
forExpr, s.TagName, s.Unique, s.Descending))
g.writeln("dbf.KeyEvalFunc = nil")
g.indent--
g.writeln("}")

View File

@@ -418,6 +418,13 @@ func (p *Parser) parseArrayOrBlock() ast.Expr {
}
p.expect(token.PIPE) // closing |
// Empty block body: {||} or {|x|} → body is NIL
if p.at(token.RBRACE) {
rbrace := p.advance().Pos
nilBody := &ast.LiteralExpr{ValuePos: rbrace, Kind: token.NIL_LIT, Value: "NIL"}
return &ast.BlockExpr{LBrace: lbrace, Params: params, Body: nilBody, RBrace: rbrace}
}
// Parse block body — may have comma-separated expressions
// {|x| expr1, expr2} → comma = sequence, returns last value
body := p.parseExpr()

View File

@@ -1711,18 +1711,20 @@ func (p *Parser) parseIndex() *ast.IndexCmd {
p.expect(token.ON)
keyExpr := p.parseExpr()
// INDEX ON expr TO file OR INDEX ON expr TAG tagname [TO file]
// INDEX ON expr [TAG tagname] TO file [FOR cond] [UNIQUE] [DESCENDING]
var fileExpr ast.Expr
var tagName string
if p.match(token.TO) {
fileExpr = p.parseExpr()
p.consumeFileExtension(fileExpr)
} else if p.current.Kind == token.IDENT && p.currentUpper() == "TAG" {
p.advance() // skip TAG
tagExpr := p.parseExpr() // tag name
tagName = p.expectMethodName().Literal // capture tag name
if p.match(token.TO) {
fileExpr = p.parseExpr()
} else {
fileExpr = tagExpr // use tag name as file
// TAG without TO: use tag name as file name
fileExpr = &ast.IdentExpr{NamePos: p.current.Pos, Name: tagName}
}
} else {
fileExpr = p.parseExpr() // fallback
@@ -1747,7 +1749,7 @@ func (p *Parser) parseIndex() *ast.IndexCmd {
return &ast.IndexCmd{
IndexPos: pos, KeyExpr: keyExpr, File: fileExpr,
ForCond: forCond, Unique: unique, Descending: descending,
ForCond: forCond, TagName: tagName, Unique: unique, Descending: descending,
}
}

511
hbrdd/cdx/build.go Normal file
View File

@@ -0,0 +1,511 @@
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// CDX compound index creation.
// Builds Harbour-compatible CDX files with bit-packed leaf pages.
package cdx
import (
"encoding/binary"
"io"
"math/bits"
"os"
"strings"
"five/hbrdd/ntx"
)
// cdxTagMeta holds metadata for a tag during file assembly.
type cdxTagMeta struct {
name string
headerOff int64
rootPage uint32
keyExpr string
forExpr string
keyLen int
unique bool
desc bool
}
// CreateOrAddTag creates a CDX file with a new tag, or appends a tag to
// an existing CDX file. The existing file's bytes are preserved verbatim;
// only the compound root directory is rebuilt to include the new tag.
//
// Harbour: ordCreate() → hb_cdxIndexCreateTag()
func CreateOrAddTag(path string, tagName, keyExpr, forExpr string,
keyLen int, unique, descending bool, keys []ntx.KeyRecord) (*Index, error) {
if !strings.HasSuffix(strings.ToLower(path), ".cdx") {
path += ".cdx"
}
tagName = strings.ToUpper(tagName)
if len(tagName) > MaxTagNameLen {
tagName = tagName[:MaxTagNameLen]
}
// Read existing file contents (if any) and tag metadata
var existingData []byte
var existingTags []cdxTagMeta
if fi, err := os.Stat(path); err == nil && fi.Size() > 0 {
existing, err := OpenIndex(path)
if err == nil {
for _, t := range existing.Tags() {
if strings.ToUpper(t.Name) == tagName {
continue // will be replaced
}
existingTags = append(existingTags, cdxTagMeta{
name: t.Name,
headerOff: t.HeaderOffset(),
rootPage: t.RootPtr(),
keyExpr: t.KeyExpr(),
forExpr: t.ForExpr(),
keyLen: t.KeyLen(),
})
}
existing.Close()
}
// Read entire old file
existingData, _ = os.ReadFile(path)
}
// Create/truncate the file
f, err := os.Create(path)
if err != nil {
return nil, err
}
var appendOff int64 // where to start writing new data
if len(existingData) > 0 {
// Write back existing data verbatim (preserves all old tag B-trees)
f.Write(existingData)
appendOff = int64(len(existingData))
// Align to HeaderLen boundary for the new tag header
if appendOff%int64(HeaderLen) != 0 {
appendOff = (appendOff/int64(HeaderLen) + 1) * int64(HeaderLen)
}
} else {
// New file: reserve space for compound root header
appendOff = int64(HeaderLen)
}
// Write the new tag's header + B-tree
newTagHeaderOff := appendOff
appendOff += int64(HeaderLen) // reserve header space
// Build B-tree pages for the new tag
var rootPageOff uint32
if len(keys) == 0 {
pg := make([]byte, PageLen)
binary.LittleEndian.PutUint16(pg[0:2], NodeLeaf|NodeRoot)
f.WriteAt(pg, appendOff)
rootPageOff = uint32(appendOff)
appendOff += PageLen
} else {
root, next := buildCDXBTree(f, keys, keyLen, appendOff)
rootPageOff = uint32(root)
appendOff = next
}
// Write the new tag header
writeTagHeader(f, newTagHeaderOff, rootPageOff, keyExpr, forExpr,
uint16(keyLen), unique, descending)
newTag := cdxTagMeta{
name: tagName, headerOff: newTagHeaderOff, rootPage: rootPageOff,
keyExpr: keyExpr, forExpr: forExpr, keyLen: keyLen,
unique: unique, desc: descending,
}
// Collect all tags (existing + new) in offset order (= creation order)
allTags := append(existingTags, newTag)
// Rebuild compound root directory page
compoundPageOff := appendOff
appendOff += PageLen
writeCompoundLeaf(f, compoundPageOff, allTags)
// Write compound root header at offset 0
writeCompoundHeader(f, uint32(compoundPageOff), len(allTags))
f.Close()
return OpenIndex(path)
}
// --- Tag header writing ---
func writeTagHeader(f *os.File, offset int64, rootPtr uint32,
keyExpr, forExpr string, keySize uint16, unique, desc bool) {
buf := make([]byte, HeaderLen)
binary.LittleEndian.PutUint32(buf[0:4], rootPtr)
binary.LittleEndian.PutUint32(buf[8:12], 1) // counter
binary.LittleEndian.PutUint16(buf[12:14], keySize)
opt := byte(TypeCompact)
if unique {
opt |= TypeUnique
}
if forExpr != "" {
opt |= TypeForFilter
}
buf[14] = opt
buf[15] = 0x01
binary.LittleEndian.PutUint16(buf[16:18], uint16(HeaderLen))
binary.LittleEndian.PutUint16(buf[18:20], uint16(PageLen))
if desc {
binary.LittleEndian.PutUint16(buf[504:506], 1)
}
copy(buf[512:], []byte(keyExpr))
if forExpr != "" {
forOff := 512 + len(keyExpr) + 1
if forOff+len(forExpr) < HeaderLen {
copy(buf[forOff:], []byte(forExpr))
}
}
f.WriteAt(buf, offset)
}
// --- Compound root ---
func writeCompoundHeader(f *os.File, rootPagePtr uint32, nTags int) {
hdr := make([]byte, HeaderLen)
binary.LittleEndian.PutUint32(hdr[0:4], rootPagePtr)
binary.LittleEndian.PutUint32(hdr[8:12], 1)
binary.LittleEndian.PutUint16(hdr[12:14], MaxTagNameLen)
hdr[14] = TypeCompound | TypeStructure | TypeCompact
hdr[15] = 0x01
binary.LittleEndian.PutUint16(hdr[16:18], uint16(HeaderLen))
binary.LittleEndian.PutUint16(hdr[18:20], uint16(PageLen))
f.WriteAt(hdr, 0)
}
func writeCompoundLeaf(f *os.File, offset int64, tags []cdxTagMeta) {
leaf := make([]byte, PageLen)
nTags := len(tags)
compKeyLen := MaxTagNameLen
maxOff := uint32(0)
for _, t := range tags {
if uint32(t.headerOff) > maxOff {
maxOff = uint32(t.headerOff)
}
}
recBits := bitsNeeded(maxOff)
dupBits := bitsNeeded(uint32(compKeyLen))
trlBits := bitsNeeded(uint32(compKeyLen))
keyBytes := (recBits + dupBits + trlBits + 7) / 8
binary.LittleEndian.PutUint16(leaf[0:2], NodeLeaf|NodeRoot)
binary.LittleEndian.PutUint16(leaf[2:4], uint16(nTags))
binary.LittleEndian.PutUint32(leaf[14:18], (1<<uint(recBits))-1)
leaf[18] = byte((1 << uint(dupBits)) - 1)
leaf[19] = byte((1 << uint(trlBits)) - 1)
leaf[20] = byte(recBits)
leaf[21] = byte(dupBits)
leaf[22] = byte(trlBits)
leaf[23] = byte(keyBytes)
prevKey := make([]byte, compKeyLen)
for j := range prevKey {
prevKey[j] = ' '
}
keyDataPos := PageLen
for i, t := range tags {
key := padTagName(t.name)
dup := commonPrefix(key, prevKey, compKeyLen)
trl := trailingSpaces(key, compKeyLen)
newBytes := compKeyLen - dup - trl
if newBytes > 0 {
keyDataPos -= newBytes
copy(leaf[keyDataPos:], key[dup:dup+newBytes])
}
recNo := uint32(t.headerOff)
var val uint64
val = uint64(trl)
val <<= uint(dupBits)
val |= uint64(dup)
val <<= uint(recBits)
val |= uint64(recNo)
entryOff := ExtHeadSize + i*int(keyBytes)
for j := 0; j < int(keyBytes); j++ {
leaf[entryOff+j] = byte(val & 0xFF)
val >>= 8
}
copy(prevKey, key)
}
freeSpc := keyDataPos - (ExtHeadSize + nTags*int(keyBytes))
if freeSpc < 0 {
freeSpc = 0
}
binary.LittleEndian.PutUint16(leaf[12:14], uint16(freeSpc))
f.WriteAt(leaf, offset)
}
// --- B-tree builder ---
func buildCDXBTree(f *os.File, keys []ntx.KeyRecord, keyLen int,
startOff int64) (rootOff int64, nextOff int64) {
maxRecNo := uint32(0)
for _, k := range keys {
if k.RecNo > maxRecNo {
maxRecNo = k.RecNo
}
}
recBits := bitsNeeded(maxRecNo)
dupBits := bitsNeeded(uint32(keyLen))
trlBits := bitsNeeded(uint32(keyLen))
keyBytesPerEntry := (recBits + dupBits + trlBits + 7) / 8
maxKeysPerLeaf := (PageLen - ExtHeadSize) / (int(keyBytesPerEntry) + keyLen)
if maxKeysPerLeaf < 2 {
maxKeysPerLeaf = 2
}
intEntrySize := keyLen + 8
maxKeysPerInt := (PageLen - 12) / intEntrySize
if maxKeysPerInt < 2 {
maxKeysPerInt = 2
}
curOff := startOff
type leafInfo struct {
pageOff int64
lastKey []byte
lastRec uint32
}
var leaves []leafInfo
for i := 0; i < len(keys); {
end := i + maxKeysPerLeaf
if end > len(keys) {
end = len(keys)
}
chunk := keys[i:end]
pageOff := curOff
writeCDXLeafPage(f, pageOff, chunk, keyLen, recBits, dupBits, trlBits, keyBytesPerEntry)
curOff += PageLen
leaves = append(leaves, leafInfo{
pageOff: pageOff,
lastKey: chunk[len(chunk)-1].Key,
lastRec: chunk[len(chunk)-1].RecNo,
})
i = end
}
// Link leaf pages
for i := range leaves {
pg := make([]byte, PageLen)
f.ReadAt(pg, leaves[i].pageOff)
if i > 0 {
binary.LittleEndian.PutUint32(pg[4:8], uint32(leaves[i-1].pageOff))
}
if i < len(leaves)-1 {
binary.LittleEndian.PutUint32(pg[8:12], uint32(leaves[i+1].pageOff))
}
f.WriteAt(pg, leaves[i].pageOff)
}
if len(leaves) == 1 {
pg := make([]byte, 2)
f.ReadAt(pg, leaves[0].pageOff)
attr := binary.LittleEndian.Uint16(pg)
attr |= NodeRoot
binary.LittleEndian.PutUint16(pg, attr)
f.WriteAt(pg, leaves[0].pageOff)
return leaves[0].pageOff, curOff
}
type childInfo struct {
pageOff int64
sepKey []byte
sepRec uint32
}
children := make([]childInfo, len(leaves))
for i, l := range leaves {
children[i] = childInfo{pageOff: l.pageOff, sepKey: l.lastKey, sepRec: l.lastRec}
}
for len(children) > 1 {
var nextLevel []childInfo
for i := 0; i < len(children); {
end := i + maxKeysPerInt + 1
if end > len(children) {
end = len(children)
}
group := children[i:end]
nKeys := len(group) - 1
pg := make([]byte, PageLen)
attr := uint16(0)
if len(children) <= maxKeysPerInt+1 {
attr |= NodeRoot
}
binary.LittleEndian.PutUint16(pg[0:2], attr)
binary.LittleEndian.PutUint16(pg[2:4], uint16(nKeys))
binary.LittleEndian.PutUint32(pg[4:8], uint32(group[0].pageOff))
off := 12
for k := 0; k < nKeys; k++ {
key := group[k].sepKey
if len(key) < keyLen {
padded := make([]byte, keyLen)
copy(padded, key)
for j := len(key); j < keyLen; j++ {
padded[j] = ' '
}
key = padded
}
copy(pg[off:off+keyLen], key[:keyLen])
off += keyLen
binary.BigEndian.PutUint32(pg[off:off+4], group[k].sepRec)
off += 4
binary.BigEndian.PutUint32(pg[off:off+4], uint32(group[k+1].pageOff))
off += 4
}
pageOff := curOff
f.WriteAt(pg, pageOff)
curOff += PageLen
ci := childInfo{pageOff: pageOff}
if end < len(children) {
ci.sepKey = group[nKeys].sepKey
ci.sepRec = group[nKeys].sepRec
}
nextLevel = append(nextLevel, ci)
i = end
}
children = nextLevel
}
return children[0].pageOff, curOff
}
// --- Leaf page writer ---
func writeCDXLeafPage(f *os.File, offset int64, keys []ntx.KeyRecord,
keyLen, recBits, dupBits, trlBits, keyBytesPerEntry int) {
pg := make([]byte, PageLen)
binary.LittleEndian.PutUint16(pg[0:2], NodeLeaf)
binary.LittleEndian.PutUint16(pg[2:4], uint16(len(keys)))
binary.LittleEndian.PutUint32(pg[14:18], (1<<uint(recBits))-1)
pg[18] = byte((1 << uint(dupBits)) - 1)
pg[19] = byte((1 << uint(trlBits)) - 1)
pg[20] = byte(recBits)
pg[21] = byte(dupBits)
pg[22] = byte(trlBits)
pg[23] = byte(keyBytesPerEntry)
prevKey := make([]byte, keyLen)
for j := range prevKey {
prevKey[j] = ' '
}
keyDataPos := PageLen
for i, kr := range keys {
key := kr.Key
if len(key) < keyLen {
padded := make([]byte, keyLen)
copy(padded, key)
for j := len(key); j < keyLen; j++ {
padded[j] = ' '
}
key = padded
} else if len(key) > keyLen {
key = key[:keyLen]
}
dup := commonPrefix(key, prevKey, keyLen)
trl := trailingSpaces(key, keyLen)
newBytes := keyLen - dup - trl
if newBytes > 0 {
keyDataPos -= newBytes
copy(pg[keyDataPos:], key[dup:dup+newBytes])
}
var val uint64
val = uint64(trl)
val <<= uint(dupBits)
val |= uint64(dup)
val <<= uint(recBits)
val |= uint64(kr.RecNo)
entryOff := ExtHeadSize + i*keyBytesPerEntry
for j := 0; j < keyBytesPerEntry; j++ {
pg[entryOff+j] = byte(val & 0xFF)
val >>= 8
}
copy(prevKey, key)
}
freeSpc := keyDataPos - (ExtHeadSize + len(keys)*keyBytesPerEntry)
if freeSpc < 0 {
freeSpc = 0
}
binary.LittleEndian.PutUint16(pg[12:14], uint16(freeSpc))
f.WriteAt(pg, offset)
}
// --- Helpers ---
func bitsNeeded(maxVal uint32) int {
if maxVal == 0 {
return 1
}
return bits.Len32(maxVal)
}
func commonPrefix(a, b []byte, maxLen int) int {
n := maxLen
if len(a) < n {
n = len(a)
}
if len(b) < n {
n = len(b)
}
for i := 0; i < n; i++ {
if a[i] != b[i] {
return i
}
}
return n
}
func trailingSpaces(key []byte, keyLen int) int {
n := keyLen
if len(key) < n {
n = len(key)
}
count := 0
for i := n - 1; i >= 0; i-- {
if key[i] == ' ' {
count++
} else {
break
}
}
return count
}
func padTagName(name string) []byte {
b := make([]byte, MaxTagNameLen)
copy(b, []byte(strings.ToUpper(name)))
for i := len(name); i < MaxTagNameLen; i++ {
b[i] = ' '
}
return b
}
// Ensure io import is used (for potential future reads)
var _ = io.EOF

View File

@@ -937,6 +937,12 @@ func (t *Tag) ForExpr() string { return t.header.ForExpr }
// IsDescending returns true if the tag sorts in descending order.
func (t *Tag) IsDescending() bool { return t.header.Descending }
// HeaderOffset returns the file offset of this tag's header block.
func (t *Tag) HeaderOffset() int64 { return t.headerOff }
// RootPtr returns the root page offset for this tag's B-tree.
func (t *Tag) RootPtr() uint32 { return t.header.RootPtr }
// Close is a no-op for tags (the parent Index owns the file).
func (t *Tag) Close() error { return nil }

View File

@@ -105,9 +105,14 @@ func (a *DBFArea) OrderCreate(params hbrdd.OrderCreateParams) error {
return fmt.Errorf("index file path required")
}
// Ensure .ntx extension
// Determine index format: CDX if TAG specified or .cdx extension, otherwise NTX
useCDX := params.TagName != "" || strings.HasSuffix(strings.ToLower(idxPath), ".cdx")
if !strings.Contains(filepath.Base(idxPath), ".") {
idxPath += ".ntx"
if useCDX {
idxPath += ".cdx"
} else {
idxPath += ".ntx"
}
}
// Build key evaluator from expression
@@ -232,15 +237,52 @@ func (a *DBFArea) OrderCreate(params hbrdd.OrderCreateParams) error {
sort.Sort(keyRecordAsc(keys))
}
idx, err := ntx.CreateIndex(idxPath, keyExpr, keyLen, params.Unique, params.Descending, keys)
if err != nil {
return fmt.Errorf("create index failed: %w", err)
if useCDX {
// CDX compound index — append tag to existing file or create new
tagName := params.TagName
if tagName == "" {
tagName = keyExpr // default tag name = key expression
}
ci, err := cdx.CreateOrAddTag(idxPath, tagName, keyExpr, params.ForExpr,
keyLen, params.Unique, params.Descending, keys)
if err != nil {
return fmt.Errorf("create CDX index failed: %w", err)
}
// Register all tags from the CDX file
// If this is the first tag, add all; if adding to existing, re-register
// Remove old entries for this CDX file first
newIndexes := make([]IndexEngine, 0, len(a.idxState.indexes)+ci.TagCount())
newNames := make([]string, 0, cap(newIndexes))
newTags := make([]string, 0, cap(newIndexes))
newKeyExprs := make([]string, 0, cap(newIndexes))
for i, name := range a.idxState.names {
if name != idxPath {
newIndexes = append(newIndexes, a.idxState.indexes[i])
newNames = append(newNames, a.idxState.names[i])
newTags = append(newTags, a.idxState.tags[i])
newKeyExprs = append(newKeyExprs, a.idxState.keyExprs[i])
}
}
for _, tag := range ci.Tags() {
newIndexes = append(newIndexes, tag)
newNames = append(newNames, idxPath)
newTags = append(newTags, tag.Name)
newKeyExprs = append(newKeyExprs, tag.KeyExpr())
}
a.idxState.indexes = newIndexes
a.idxState.names = newNames
a.idxState.tags = newTags
a.idxState.keyExprs = newKeyExprs
} else {
idx, err := ntx.CreateIndex(idxPath, keyExpr, keyLen, params.Unique, params.Descending, keys)
if err != nil {
return fmt.Errorf("create index failed: %w", err)
}
a.idxState.indexes = append(a.idxState.indexes, idx)
a.idxState.names = append(a.idxState.names, idxPath)
a.idxState.tags = append(a.idxState.tags, params.TagName)
a.idxState.keyExprs = append(a.idxState.keyExprs, keyExpr)
}
a.idxState.indexes = append(a.idxState.indexes, idx)
a.idxState.names = append(a.idxState.names, idxPath)
a.idxState.tags = append(a.idxState.tags, params.TagName)
a.idxState.keyExprs = append(a.idxState.keyExprs, keyExpr)
a.idxState.current = len(a.idxState.indexes) - 1
return nil