Skip to content
totoerPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

docid

Встраиваемая документо-ориентированная база данных для Go 1.24.3+.

  • Pebble хранит документы и определения индексов на диске.
  • google/btree используется для вторичных индексов в памяти.
  • Документы — обычные JSON-объекты, объединённые в коллекции.
  • Go DSL поддерживает фильтры, INNER/LEFT JOIN, сортировку и пагинацию.

Содержание

Подключение

Нужен Go 1.24.3 или новее. Модуль библиотеки — github.com/totoer/docid.

В каталоге вашего Go-модуля выполните:

go get github.com/totoer/docid

Для нового приложения сначала создайте модуль:

mkdir docid-demo
cd docid-demo
go mod init example.com/docid-demo
go get github.com/totoer/docid

Быстрый старт

Создайте main.go в каталоге приложения:

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/totoer/docid"
)

type User struct {
    ID   string `json:"id"`
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}

func run() (err error) {
    db, err := docid.Open("./data")
    if err != nil {
        return err
    }
    defer func() {
        if closeErr := db.Close(); err == nil {
            err = closeErr
        }
    }()

    users, err := db.Collection("users")
    if err != nil {
        return err
    }
    if err := users.Put("u1", User{ID: "u1", Name: "Анна", Age: 28}); err != nil {
        return err
    }

    var user User
    if err := users.GetInto("u1", &user); err != nil {
        return err
    }
    fmt.Println("Пользователь:", user.Name)

    var adults []User
    if err := users.Find().As("u").
        Where(docid.F("u.age").Gte(18)).
        OrderBy(docid.Asc(docid.F("u.name"))).
        SelectInto(context.Background(), "u", &adults); err != nil {
        return err
    }
    fmt.Println("Совершеннолетних:", len(adults))
    return nil
}

Запустите приложение:

go run .

В новом каталоге базы приложение выведет:

Пользователь: Анна
Совершеннолетних: 1

Каталог ./data создаётся относительно рабочей директории и сохраняется между запусками. Пример использует Put, поэтому повторный запуск заменяет документ u1 и даёт тот же результат. Для создания документа с проверкой отсутствия ключа используйте Insert.

Пример с JOIN находится в example_test.go; из каталога библиотеки его можно выполнить командой go test -run '^Example$' -v ..

Остальные примеры — фрагменты функций, возвращающих error. Они предполагают открытый db, заранее полученные коллекции (users, orders, payments) и необходимые импорты: github.com/totoer/docid, context, encoding/json, errors, fmt.

Документы и коллекции

err := users.Insert("u2", docid.Document{
    "id": "u2",
    "name": "Борис",
    "age": 32,
    "address": map[string]any{"city": "Белград"},
})
if err != nil {
    return err
}

user, err := users.Get("u2")
if err != nil {
    return err
}
fmt.Println(user["name"])

Insert принимает структуры с JSON-тегами, карты и json.RawMessage. Массив, примитив и null на верхнем уровне отклоняются с ErrInvalidDocument. Числа при чтении представлены json.Number, чтобы сохранять точность больших целых и десятичных значений.

ID — отдельный непустой строковый ключ в UTF-8 внутри коллекции. Он не добавляется в JSON автоматически. Если ID нужен в условии JOIN, сохраните его также как обычное поле документа, например id.

Метод Поведение
db.Collection(name) Возвращает коллекцию, создавая её при необходимости
db.Collections() Возвращает имена коллекций в лексикографическом порядке
users.Insert(id, value) Создаёт документ; при занятом ключе возвращает ErrAlreadyExists
users.Put(id, value) Создаёт документ или целиком заменяет существующий
users.Get(id) Возвращает Document; при неизвестном ключе — ErrNotFound
users.Delete(id) Удаляет документ; при неизвестном ключе — ErrNotFound

Имена коллекций и индексов должны быть непустыми строками в UTF-8 без пробелов по краям. Некорректное имя или ID при записи возвращает ErrInvalidName.

Изменение карты, полученной через Get или запрос, не изменяет сохранённый документ: для записи используйте Put. Частичного обновления полей нет.

Транзакции

db.Update(ctx, func(tx *docid.Tx) error) error выполняет callback один раз под эксклюзивной блокировкой базы. Возврат nil фиксирует все изменения одним атомарным batch с pebble.Sync; ошибка callback отменяет их и возвращается вызывающему коду. Паника отменяет изменения и продолжает распространяться с исходным значением. Создание коллекции через tx.Collection(name) тоже входит в транзакцию; tx.Collections() видит её до фиксации.

Для существующего счёта a1 можно проверить остаток и записать операцию в другой коллекции:

err = db.Update(context.Background(), func(tx *docid.Tx) error {
    accounts, err := tx.Collection("accounts")
    if err != nil {
        return err
    }
    var account struct {
        Balance int `json:"balance"`
    }
    if err := accounts.GetInto("a1", &account); err != nil {
        return err
    }
    if account.Balance < 40 {
        return errors.New("недостаточный остаток")
    }
    account.Balance -= 40
    if err := accounts.Put("a1", account); err != nil {
        return err
    }
    ledger, err := tx.Collection("ledger")
    if err != nil {
        return err
    }
    return ledger.Insert("op1", docid.Document{"account_id": "a1", "amount": 40})
})

TxCollection предоставляет Insert, Put, Get, GetInto и Delete. Чтения видят предыдущие успешные записи и удаления callback. Ошибка валидации, занятого ID или уникальности оставляет состояние до отклонённой операции: callback может обработать её и продолжить. Для полного отката верните ошибку из callback.

Уникальность проверяется при каждой записи. После удаления документа или изменения его уникального поля значение можно присвоить другому документу в той же транзакции. Прямой обмен двумя занятыми уникальными значениями через два Put отклоняется первой проверкой; сначала нужно освободить значение. Индексы публикуются вместе с документами и остаются прежними при откате.

Транзакционный GetInto сохраняет правила JSON-декодирования и не меняет приёмник при ошибке. Его пользовательские декодеры работают внутри транзакции и могут последовательно вызывать методы tx. Обычные чтения вне транзакции по-прежнему освобождают блокировку до вызова декодера. Компилируемый пример с подготовкой данных — ExampleDB_Update в example_test.go.

tx.From и TxCollection.Find поддерживают тот же DSL, включая JOIN, сортировку, пагинацию, AllInto и SelectInto. Все коллекции и индексы запроса используют одно транзакционное состояние и видят собственные записи и удаления. Сам запрос неизвестную коллекцию не создаёт. Например, для подготовленных users и orders:

var rows []docid.Row
err = db.Update(context.Background(), func(tx *docid.Tx) error {
    var err error
    rows, err = tx.From("users").As("u").
        InnerJoin("orders", "o", docid.F("u.id").EqField(docid.F("o.user_id"))).
        Where(docid.F("o.total").Gt(0)).
        All(context.Background())
    return err
})

Пример с созданием связанных документов и JOIN до фиксации — ExampleTx_From в example_test.go. Для типизированного результата используйте AllInto(ctx, &rows) либо SelectInto(ctx, alias, &values): правила JSON, nullable LEFT JOIN и неизменность приёмника при ошибке сохраняются. Декодеры выполняются внутри callback, поэтому используют только активную транзакцию для повторного доступа к этой базе. Builder можно разветвлять и повторно выполнять внутри callback, но выполнение после завершения транзакции возвращает ErrTxClosed.

Контекст запроса действует дополнительно к контексту Update. Отмену отдельного запроса можно обработать и продолжить транзакцию; отмена Update запрещает её фиксацию. Оба контекста проверяются во время обработки результатов и перед заменой типизированного приёмника.

Остальные операции с этой базой, включая чтения, другую транзакцию и Close, ждут завершения callback. Внутри callback обращайтесь к базе только через tx и полученные из него коллекции: обычные db/Collection, вложенный Update и Close будут ждать блокировку самой транзакции. Это относится и к пользовательским JSON hooks. Не ожидайте из callback завершения другой операции с той же базой.

Транзакционные объекты используются последовательно, без параллельных goroutine, и действуют только внутри callback. Сохранённые Tx и handles после завершения возвращают ErrTxClosed при доступе к данным. Callback не повторяется автоматически. Ручных Begin/Commit/Rollback, savepoints, вложенных транзакций и изменения определений индексов внутри транзакции нет; создавайте индексы заранее.

Контекст проверяется до callback, при операциях и перед фиксацией. Отмена контекста Update до начала фиксации отменяет изменения, даже если callback обработал ошибку операции и вернул nil. Ошибка самого callback имеет приоритет над отменой. Ожидание блокировки не прерывается немедленно; выполняющийся callback должен сам вернуть управление. Начатая синхронная фиксация не прерывается: поздняя отмена не заменяет успешный результат на context.Canceled. Внешние эффекты callback, например отправка сообщения, транзакцией не откатываются.

Чтение в пользовательские структуры

GetInto(id, &value) читает документ непосредственно в пользовательский тип:

type User struct {
    ID   string `json:"id"`
    Name string `json:"name"`
    Age  int    `json:"age"`
}

var user User
if err := users.GetInto("u1", &user); err != nil {
    return err
}

var adults []User
if err := users.Find().As("u").
    Where(docid.F("u.age").Gte(18)).
    OrderBy(docid.Asc(docid.F("u.name"))).
    SelectInto(context.Background(), "u", &adults); err != nil {
    return err
}

SelectInto(ctx, alias, &slice) извлекает документы выбранного псевдонима в []User или []*User. Условия, JOIN, сортировка и пагинация применяются как при All. JOIN может повторять один документ в нескольких элементах. Неизвестный псевдоним возвращает ErrInvalidQuery, даже если результат пуст.

AllInto(ctx, &slice) сохраняет структуру строк All: поля элемента соответствуют псевдонимам запроса. Например, для JOIN:

type Order struct {
    Total int `json:"total"`
}
var rows []struct {
    User  User   `json:"u"`
    Order *Order `json:"o"`
}
err = db.From("users").As("u").
    LeftJoin("orders", "o", docid.F("u.id").EqField(docid.F("o.user_id"))).
    AllInto(context.Background(), &rows)

При отсутствии совпадения LEFT JOIN поле Order будет nil. Аналогично SelectInto(ctx, "o", &orders) возвращает nil для соответствующего элемента []*Order; в []Order получится нулевое значение.

Декодирование следует правилам encoding/json: поддерживаются JSON-теги, вложенные структуры, указатели и json.Unmarshaler (в том числе time.Time); неизвестные поля игнорируются. Числа в полях any остаются json.Number, а в типизированных полях проверяются тип и диапазон. ID из ключа коллекции автоматически не добавляется.

SelectInto и AllInto декодируют сохранённый JSON документов, как GetInto, без повторной сериализации карт Document. Пользовательские JSON-декодеры получают исходное представление документа; канонический порядок полей не гарантируется. Для запроса без условий, JOIN и сортировки промежуточные карты документов не создаются. Данные для декодирования копируются из согласованного снимка запроса; вне транзакции пользовательские декодеры вызываются после освобождения блокировки базы.

Передавайте ненулевой указатель; для методов запроса — указатель на срез. Некорректный приёмник возвращает ErrInvalidDestination. При успехе значение полностью заменяется, отсутствующие поля обнуляются, пустой результат запроса даёт пустой срез, отличный от nil. При ошибке приёмник не изменяется. Ошибки JSON-декодирования доступны через errors.As, например как *json.UnmarshalTypeError; ошибки хранения и запроса сохраняют прежние значения для errors.Is.

DSL поиска

DSL встроен в Go. Поле задаётся через F("alias.path.to.field"). Без As имя коллекции служит псевдонимом.

rows, err := db.From("users").As("u").
    Where(docid.And(
        docid.F("u.age").Gte(18),
        docid.F("u.address.city").In("Белград", "Нови-Сад"),
        docid.Not(docid.F("u.deleted_at").Exists()),
    )).
    OrderBy(docid.Desc(docid.F("u.age")), docid.Asc(docid.F("u.name"))).
    Offset(20).
    Limit(10).
    All(context.Background())

users.Find() эквивалентно db.From("users"). Результат — []Row, где Row имеет тип map[string]Document: например, rows[0]["u"]["name"].

Выражение Действие
F("u.age").Eq(18) / .Ne(18) Равенство / неравенство
.Gt(18) / .Gte(18) / .Lt(18) / .Lte(18) Сравнение
F("u.city").In("A", "B") Принадлежность набору
F("u.id").EqField(F("o.user_id")) Равенство полей
F("u.name").Exists() Наличие поля, включая явный null
F("u.name").IsNull() Только явный null
And(a, b) / Or(a, b) / Not(a) Логические операции

Повторные Where объединяются через AND. And() истинен, Or() и In() без аргументов ложны. Builder неизменяем: от одного запроса можно создавать независимые варианты и выполнять их параллельно.

Сравниваются скалярные JSON-значения. Числа сравниваются точно: 1, 1.0 и 1e0 равны, а 9007199254740993 отличается от 9007199254740992. Большие показатели степени сравниваются без разворачивания числа в память. Между разными типами Eq и упорядоченные сравнения ложны, Ne истинен. Массивы и объекты не участвуют в сравнениях; сравнение с отсутствующим полем всегда ложно, включая Ne. Not инвертирует булев результат, поэтому Not(F("u.x").Eq(1)) также включает документы без x.

Сортировка по возрастанию: отсутствующее поле или нескалярное значение, затем null, bool, число, строка. Строки сравниваются по байтам UTF-8. При равенстве сохраняется исходный порядок по ID документов и ID присоединённых документов. Без OrderBy используется этот же порядок. Limit(0) возвращает пустой результат; без Limit ограничения нет.

Отрицательные Limit/Offset, неизвестные псевдонимы и некорректные выражения возвращают ErrInvalidQuery при выполнении. Неизвестная коллекция возвращает ErrCollectionNotFound; запрос сам коллекции не создаёт. Ошибки можно проверять через errors.Is.

Пути с точкой адресуют вложенные объекты; экранирование ключей с точками и адресация элементов массивов не поддерживаются. Псевдоним не должен содержать точку. Для коллекции с точкой в имени задайте псевдоним через As.

JOIN

rows, err := db.From("users").As("u").
    InnerJoin("orders", "o",
        docid.F("u.id").EqField(docid.F("o.user_id")),
    ).
    LeftJoin("payments", "p",
        docid.F("o.id").EqField(docid.F("p.order_id")),
    ).
    Where(docid.F("o.total").Gt(1000)).
    OrderBy(docid.Desc(docid.F("o.total"))).
    All(context.Background())

INNER JOIN возвращает совпавшие пары. LEFT JOIN сохраняет исходную строку при отсутствии совпадений; в таком случае row["p"] == nil. Связь «один ко многим» создаёт отдельную строку на каждое совпадение. Возможны цепочки JOIN и соединение коллекции с собой под разными псевдонимами.

ON принимает произвольное выражение DSL и может ссылаться на текущий и ранее объявленные псевдонимы. Например, And(EqField(...), F("o.total").Gt(1000)). Условия ON проверяются во время соединения, а Where — после всех соединений. Фильтр по присоединённому полю в Where может исключить строки LEFT JOIN без совпадения. Явные null равны друг другу; отсутствующие поля друг другу не равны.

Для равенства полей JOIN использует индекс присоединяемой коллекции, если он есть, в том числе для равенства внутри AND. В остальных случаях выполняется вложенный перебор.

Индексы

if err := users.CreateIndex(docid.Index{
    Name: "unique_email",
    Field: "email",
    Unique: true,
}); err != nil {
    return err
}
// Для ускорения JOIN по orders.user_id:
if err := orders.CreateIndex(docid.Index{Name: "by_user", Field: "user_id"}); err != nil {
    return err
}

Индексы создаются по существующим документам и автоматически обновляются при Insert, Put, Delete. Indexes() возвращает определения, DropIndex(name) удаляет индекс. Нарушение уникальности возвращает ErrUniqueConstraint и не изменяет документ. Неудачное создание уникального индекса не сохраняет его определение.

Инициализация при повторном запуске

Определения индексов сохраняются на диске. Повторный CreateIndex с тем же именем возвращает ErrAlreadyExists, даже если определение совпадает. При старте приложения используйте проверку определения, чтобы заметить изменение поля или требования уникальности:

func ensureIndex(collection *docid.Collection, want docid.Index) error {
    err := collection.CreateIndex(want)
    if !errors.Is(err, docid.ErrAlreadyExists) {
        return err
    }
    indexes, err := collection.Indexes()
    if err != nil {
        return err
    }
    for _, actual := range indexes {
        if actual.Name == want.Name {
            if actual != want {
                return fmt.Errorf("индекс %q: сохранено %+v, требуется %+v", want.Name, actual, want)
            }
            return nil
        }
    }
    return fmt.Errorf("индекс %q отсутствует после ErrAlreadyExists", want.Name)
}

Вызовите эту функцию после получения коллекции, до запуска обработки запросов:

if err := ensureIndex(users, docid.Index{
    Name: "unique_email",
    Field: "email",
    Unique: true,
}); err != nil {
    return err
}

Для изменения определения удалите старый индекс через DropIndex и создайте новый. Эти операции выполняются отдельно; создание уникального индекса может завершиться ошибкой, если существующие документы нарушают уникальность.

Использование индексов

Индексы разреженные: отсутствующее поле, массив или объект пропускаются. Явный null индексируется и участвует в уникальности. Индекс покрывает одно поле, включая вложенное. Оптимизатор использует индекс для Eq, Gt, Gte, Lt, Lte исходной коллекции и выбирает подходящее условие внутри AND. OR, NOT и IN проверяются перебором. Полное условие всегда проверяется после получения кандидатов.

Текстовые индексы

Именованный текстовый индекс покрывает несколько JSON-полей, включая вложенные. Индексируются только строки; null, отсутствующие поля, числа, bool, массивы и объекты не преобразуются в текст. Позиции слов независимы в каждом поле.

if err := docs.CreateTextIndex(docid.TextIndex{
    Name: "search",
    Fields: []string{"title", "body"},
    Tokenizer: "unicode61 remove_diacritics 2",
}); err != nil {
    return err
}
definitions, err := docs.TextIndexes()
if err != nil {
    return err
}
fmt.Println(definitions)

Match(alias, indexName, query) — булев фильтр в существующем DSL:

rows, err := docs.Find().As("d").
    Where(docid.And(
        docid.Match("d", "search", `title:докум* OR body:"база данных"`),
        docid.F("d.published").Eq(true),
    )).
    OrderBy(docid.Desc(docid.F("d.created_at"))).
    Limit(20).
    All(context.Background())
if err != nil {
    return err
}
fmt.Println(len(rows))

Строка запроса использует синтаксис FTS5: база данных требует оба слова, "база данных" — последовательную фразу, докум* — префикс, NEAR(база данных, 5) — близость, ^база — первое слово поля. Доступны AND, OR, бинарный NOT, скобки, фильтры title:слово, {title body}:слово, -title:слово. Приоритеты: неявный AND, NOT, AND, OR. Имена операторов регистрозависимы. Символ * внутри кавычек не является оператором префикса. Кавычка в строке экранируется удвоением "".

Вложенный путь из определения индекса используется как имя колонки в кавычках: "article.title":документ. Поиск не переносит фразы или NEAR через границы полей. Для LIKE, поиска произвольных подстрок и русских словоформ этот фильтр не предназначен. Score и сортировки по релевантности нет; действуют обычные OrderBy, Offset/Limit, AllInto и SelectInto.

Индекс обязателен: отсутствующий индекс, неизвестное поле, некорректная строка MATCH или alias дают ErrInvalidQuery, в том числе на пустой коллекции и при Limit(0). Неизвестная коллекция по-прежнему даёт ErrCollectionNotFound.

Внутри MATCH выражение a NOT b означает «a и отсутствие b». Внешний docid.Not(docid.Match(...)) инвертирует фильтр целиком и включает документы без индексируемого текста. Для отсутствующего документа LEFT JOIN сам Match ложен, а внешний Not истинен. Match доступен как в ON, так и в Where, сохраняя различие между этими стадиями JOIN.

В Update используйте tx.From или TxCollection.Find: поиск видит предыдущие успешные записи и удаления callback. Индекс создаётся заранее через обычный Collection, а откат отменяет и документы, и текстовые изменения. Примеры ExampleMatch и ExampleTx_From_textSearch находятся в text_example_test.go.

Пустой Tokenizer означает unicode61; TextIndexes возвращает в этом случае явное значение unicode61. Поддерживаются remove_diacritics 0/1/2, categories, tokenchars и separators. Правила регистра, категорий и диакритики соответствуют Unicode 6.1 из SQLite; стемминга нет.

CreateTextIndex строит индекс по существующим документам. Повторное имя возвращает ErrAlreadyExists. При инициализации приложения сверяйте поля через slices.Equal(actual.Fields, want.Fields) и строку Tokenizer: структура TextIndex содержит срез и не сравнивается через ==. Имена текстовых и скалярных индексов независимы; Indexes и DropIndex работают только со скалярными, TextIndexes и DropTextIndex — только с текстовыми.

Определения сохраняются в Pebble. Индексы находятся в RAM и восстанавливаются из документов при Open. Число вхождений слов влияет на память, стоимость записи и время открытия. Создание и удаление индекса удерживает блокировку базы. Для изменения определения вызовите DropTextIndex и CreateTextIndex вне Update; транзакционного изменения определений нет. Между этими вызовами индекс отсутствует. Обычный и транзакционный CRUD обновляет индекс вместе с документами; откат не публикует текстовые изменения.

Старые базы открываются новой версией без миграции. Для отката на версию без текстового поиска остановите приложение, сделайте резервную копию и удалите все текстовые индексы новой версией до запуска старой. Старый бинарник не знает их определений и не обеспечивает согласованность текстового поиска.

Эталон, генерация фикстур и ограничения совместимости описаны в руководстве.

Обработка ошибок

Проверяйте ошибки через errors.Is, поскольку они могут содержать дополнительный контекст:

user, err := users.Get("u1")
if errors.Is(err, docid.ErrNotFound) {
    return fmt.Errorf("пользователь u1 отсутствует: %w", err)
}
if err != nil {
    return err
}
fmt.Println(user["name"])

if err := users.Put("u2", docid.Document{"email": "anna@example.com"}); err != nil {
    if errors.Is(err, docid.ErrUniqueConstraint) {
        return fmt.Errorf("email уже занят: %w", err)
    }
    return err
}

Проверка уникальности в примере предполагает созданный уникальный индекс по email.

Ошибка Причина
ErrNotFound Документ или удаляемый индекс не найден
ErrAlreadyExists Ключ документа при Insert или имя индекса при CreateIndex уже заняты
ErrUniqueConstraint Запись документа или создание индекса нарушает уникальность
ErrCollectionNotFound В запросе указана несуществующая коллекция
ErrInvalidDocument Значение не сериализуется в корректный JSON-объект
ErrInvalidName Некорректное имя коллекции, определение индекса или ID при записи
ErrInvalidQuery Некорректное выражение, псевдоним, пагинация, MATCH, отсутствующий текстовый индекс или nil вместо контекста
ErrInvalidDestination Некорректный приёмник для GetInto, SelectInto или AllInto
ErrClosed Операция с закрытой базой
ErrInvalidTransaction nil вместо контекста или callback в Update
ErrTxClosed Доступ к данным через завершённую или нулевую транзакцию

Операции также могут возвращать ошибки хранилища, JSON-декодирования и отмены контекста. Для типизированных ошибок JSON используйте errors.As.

Хранение и конкурентность

Документ сохраняется в Pebble как непосредственно сериализованный JSON-объект, без оболочки и служебных полей. Коллекции, ID и определения индексов имеют отдельные ключи. Имена кодируются для изоляции пространств ключей. Обычная запись использует pebble.Sync; Update синхронно фиксирует все изменения одним batch. Определения индексов сохраняются; B-деревья восстанавливаются из документов при Open.

Один DB безопасен для нескольких goroutine. Запись эксклюзивна; чтения могут выполняться параллельно. Запрос удерживает блокировку чтения на всё выполнение и видит согласованное состояние всех участвующих коллекций. Close ждёт текущие операции, повторный вызов успешен; дальнейшие операции возвращают ErrClosed. Каталог открывается одним процессом благодаря блокировке Pebble.

Ограничения

  • Индексы целиком размещаются в RAM; запуск с индексами требует чтения документов.
  • Запрос материализует промежуточные строки и результат в памяти. Внутренние строки содержат ссылки на документы; JOIN копирует строку только при совпадении, а All создаёт карты только для итоговой страницы. Значения сортировки нормализуются один раз на строку.
  • Отмена context.Context проверяется во время чтения и обработки строк, но ожидание блокировки, отдельный обход B-tree и сортировка не прерываются немедленно.
  • Частичных обновлений, агрегаций и строкового SQL-парсера нет. Транзакция блокирует всю базу до завершения callback.
  • Карты документов могут разделяться между строками одного результата; при независимом изменении строк скопируйте соответствующий документ, включая изменяемые вложенные карты и срезы.

Разработка docid

Из корня репозитория выполните:

go test ./...
go test -race ./...
go vet ./...

Тесты покрывают CRUD, валидацию JSON, изоляцию коллекций, точность чисел, фильтры с индексом и без него, обновление и уникальность индексов, восстановление после перезапуска, INNER/LEFT/self JOIN, цепочки JOIN, сортировку, пагинацию, повторное использование запросов и конкурентный доступ. Пример с JOIN выполняется как Go example test.

Бенчмарки

Быстрая проверка всех сценариев:

go test -run '^$' -bench . -benchtime=1x ./...

Для замера выбранной группы, например фильтрации:

go test -run '^$' -bench '^BenchmarkQueryFilter$' -benchmem -count=10 ./...

Один проход проверяет работоспособность и не подходит для выводов о скорости. Основная серия использует Pebble с файловой системой в памяти; запись на диск измеряется отдельно в BenchmarkCollectionDurable.

Список групп, методика и команды сравнения результатов — в руководстве по бенчмаркам.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

⌂ 换网址