before merge to main
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user