before merge to main

This commit is contained in:
2026-03-04 16:29:24 +06:00
parent 0481cde1c3
commit 491ccbea89
21 changed files with 6776 additions and 47 deletions

View File

@@ -8,6 +8,8 @@ import (
"strings"
"sync"
"time"
goccyjson "github.com/goccy/go-json"
)
// LineDb представляет основную базу данных
@@ -159,9 +161,9 @@ func (db *LineDb) Insert(data any, collectionName string, options LineDbAdapterO
resultDataArray := make([]any, 0, len(dataArray))
for _, item := range dataArray {
itemMap, ok := item.(map[string]any)
if !ok {
return fmt.Errorf("invalid data format")
itemMap, err := db.toMap(item)
if err != nil {
return fmt.Errorf("invalid data format: %w", err)
}
// Генерируем ID если отсутствует
@@ -220,7 +222,12 @@ func (db *LineDb) Insert(data any, collectionName string, options LineDbAdapterO
}
}
// Проверяем уникальность полей из UniqueFields
// Проверяем обязательные поля (required)
if err := db.checkRequiredFieldsInsert(itemMap, collectionName); err != nil {
return err
}
// Проверяем уникальность полей
if err := db.checkUniqueFieldsInsert(itemMap, collectionName, resultDataArray, options); err != nil {
return err
}
@@ -292,24 +299,32 @@ func (db *LineDb) Update(data any, collectionName string, filter any, options Li
collectionName = db.getFirstCollection()
}
// Конвертируем data в map (struct или map)
dataMap, err := db.toMap(data)
if err != nil {
return nil, fmt.Errorf("invalid update data format: %w", err)
}
// Проверяем конфликт ID
if dataMap, ok := data.(map[string]any); ok {
if filterMap, ok := filter.(map[string]any); ok {
if dataMap["id"] != nil && filterMap["id"] != nil {
if !db.compareIDs(dataMap["id"], filterMap["id"]) {
return nil, fmt.Errorf("you can not update record id with filter by another id. Use delete and insert instead")
}
if filterMap, ok := filter.(map[string]any); ok {
if dataMap["id"] != nil && filterMap["id"] != nil {
if !db.compareIDs(dataMap["id"], filterMap["id"]) {
return nil, fmt.Errorf("you can not update record id with filter by another id. Use delete and insert instead")
}
}
// Проверяем уникальность полей из UniqueFields
if err := db.checkUniqueFieldsUpdate(dataMap, filter, collectionName, options); err != nil {
return nil, err
}
}
// Проверяем обязательные поля (required)
if err := db.checkRequiredFieldsUpdate(dataMap, collectionName); err != nil {
return nil, err
}
// Проверяем уникальность полей
if err := db.checkUniqueFieldsUpdate(dataMap, filter, collectionName, options); err != nil {
return nil, err
}
// Проверяем партиционирование
if db.isCollectionPartitioned(collectionName) {
return db.updatePartitioned(data, collectionName, filter, options)
return db.updatePartitioned(dataMap, collectionName, filter, options)
}
// Обычное обновление
@@ -318,7 +333,7 @@ func (db *LineDb) Update(data any, collectionName string, filter any, options Li
return nil, fmt.Errorf("collection %s not found", collectionName)
}
return adapter.Update(data, filter, options)
return adapter.Update(dataMap, filter, options)
}
// Delete удаляет записи из коллекции
@@ -499,7 +514,7 @@ func (db *LineDb) getCollectionOptions(collectionName string) *JSONLFileOptions
return nil
}
// isValueEmpty проверяет, считается ли значение "пустым" (пропускаем проверку уникальности для пустых)
// isValueEmpty проверяет, считается ли значение "пустым" (nil или "" для string) — для обратной совместимости
func (db *LineDb) isValueEmpty(v any) bool {
if v == nil {
return true
@@ -510,20 +525,119 @@ func (db *LineDb) isValueEmpty(v any) bool {
return false
}
// isEmptyByMode проверяет, пусто ли значение по заданному режиму
func (db *LineDb) isEmptyByMode(m map[string]any, key string, mode EmptyValueMode) bool {
if mode == 0 {
mode = DefaultEmptyModeForUnique
}
v, exists := m[key]
if mode&EmptyModeAbsentKey != 0 && !exists {
return true
}
if mode&EmptyModeNil != 0 && v == nil {
return true
}
if mode&EmptyModeZeroValue != 0 {
switch val := v.(type) {
case string:
return val == ""
case int:
return val == 0
case int64:
return val == 0
case float64:
return val == 0
}
}
return false
}
// getUniqueFieldConstraints возвращает поля с unique и их EmptyMode (из FieldConstraints или UniqueFields)
func (db *LineDb) getUniqueFieldConstraints(collectionName string) []FieldConstraint {
opts := db.getCollectionOptions(collectionName)
if opts == nil {
return nil
}
if len(opts.FieldConstraints) > 0 {
var out []FieldConstraint
for _, fc := range opts.FieldConstraints {
if fc.Unique {
mode := fc.EmptyMode
if mode == 0 {
mode = DefaultEmptyModeForUnique
}
out = append(out, FieldConstraint{Name: fc.Name, Unique: true, EmptyMode: mode})
}
}
return out
}
// Legacy UniqueFields
var out []FieldConstraint
for _, name := range opts.UniqueFields {
out = append(out, FieldConstraint{Name: name, Unique: true, EmptyMode: DefaultEmptyModeForUnique})
}
return out
}
// getRequiredFieldConstraints возвращает required поля и их EmptyMode
func (db *LineDb) getRequiredFieldConstraints(collectionName string) []FieldConstraint {
opts := db.getCollectionOptions(collectionName)
if opts == nil {
return nil
}
var out []FieldConstraint
for _, fc := range opts.FieldConstraints {
if fc.Required {
mode := fc.EmptyMode
if mode == 0 {
mode = DefaultEmptyModeForRequired
}
out = append(out, FieldConstraint{Name: fc.Name, Required: true, EmptyMode: mode})
}
}
return out
}
// checkRequiredFieldsInsert проверяет обязательные поля при вставке
func (db *LineDb) checkRequiredFieldsInsert(itemMap map[string]any, collectionName string) error {
required := db.getRequiredFieldConstraints(collectionName)
for _, fc := range required {
if db.isEmptyByMode(itemMap, fc.Name, fc.EmptyMode) {
return fmt.Errorf("required field %q is empty in collection %q", fc.Name, collectionName)
}
}
return nil
}
// checkRequiredFieldsUpdate проверяет, что при Update не устанавливаются пустые значения в required полях
func (db *LineDb) checkRequiredFieldsUpdate(data map[string]any, collectionName string) error {
required := db.getRequiredFieldConstraints(collectionName)
for _, fc := range required {
if _, inData := data[fc.Name]; !inData {
continue // поле не обновляется — не проверяем
}
if db.isEmptyByMode(data, fc.Name, fc.EmptyMode) {
return fmt.Errorf("required field %q cannot be set to empty in collection %q", fc.Name, collectionName)
}
}
return nil
}
// checkUniqueFieldsInsert проверяет уникальность полей при вставке
func (db *LineDb) checkUniqueFieldsInsert(itemMap map[string]any, collectionName string, resultDataArray []any, options LineDbAdapterOptions) error {
if options.SkipCheckExistingForWrite {
return nil
}
opts := db.getCollectionOptions(collectionName)
if opts == nil || len(opts.UniqueFields) == 0 {
uniqueFields := db.getUniqueFieldConstraints(collectionName)
if len(uniqueFields) == 0 {
return nil
}
for _, fieldName := range opts.UniqueFields {
value := itemMap[fieldName]
if db.isValueEmpty(value) {
for _, fc := range uniqueFields {
fieldName := fc.Name
if db.isEmptyByMode(itemMap, fieldName, fc.EmptyMode) {
continue
}
value := itemMap[fieldName]
// Проверяем в batch (уже добавляемые записи)
for _, resultItem := range resultDataArray {
if resultMap, ok := resultItem.(map[string]any); ok {
@@ -552,8 +666,8 @@ func (db *LineDb) checkUniqueFieldsUpdate(data map[string]any, filter any, colle
if options.SkipCheckExistingForWrite {
return nil
}
opts := db.getCollectionOptions(collectionName)
if opts == nil || len(opts.UniqueFields) == 0 {
uniqueFields := db.getUniqueFieldConstraints(collectionName)
if len(uniqueFields) == 0 {
return nil
}
recordsToUpdate, err := db.ReadByFilter(filter, collectionName, LineDbAdapterOptions{InTransaction: true})
@@ -566,9 +680,10 @@ func (db *LineDb) checkUniqueFieldsUpdate(data map[string]any, filter any, colle
updatingIDs[m["id"]] = true
}
}
for _, fieldName := range opts.UniqueFields {
for _, fc := range uniqueFields {
fieldName := fc.Name
value, inData := data[fieldName]
if !inData || db.isValueEmpty(value) {
if !inData || db.isEmptyByMode(data, fieldName, fc.EmptyMode) {
continue
}
existing, err := db.ReadByFilter(map[string]any{fieldName: value}, collectionName, LineDbAdapterOptions{InTransaction: true})
@@ -707,6 +822,19 @@ func (db *LineDb) toNumber(value any) (float64, bool) {
return 0, false
}
// toMap конвертирует struct или map в map[string]any (для Insert/Update)
func (db *LineDb) toMap(v any) (map[string]any, error) {
if m, ok := v.(map[string]any); ok {
return m, nil
}
data, err := goccyjson.Marshal(v)
if err != nil {
return nil, err
}
var result map[string]any
return result, goccyjson.Unmarshal(data, &result)
}
func (db *LineDb) normalizeDataArray(data any) []any {
switch v := data.(type) {
case []any: