diff --git a/.clang-format b/.clang-format index a148e5429..130ce61da 100644 --- a/.clang-format +++ b/.clang-format @@ -3,27 +3,71 @@ Language: Cpp # BasedOnStyle: Google AccessModifierOffset: -4 AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -AlignEscapedNewlinesLeft: true -AlignOperands: true -AlignTrailingComments: true +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true -AllowShortBlocksOnASingleLine: false +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true -AlwaysBreakTemplateDeclarations: true +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability BinPackArguments: true BinPackParameters: true +BitFieldColonSpacing: Both BraceWrapping: + AfterCaseLabel: false AfterClass: false - AfterControlStatement: false + AfterControlStatement: Never AfterEnum: false + AfterExternBlock: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false @@ -31,68 +75,172 @@ BraceWrapping: AfterUnion: false BeforeCatch: false BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakArrays: true BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline BreakBeforeTernaryOperators: true -BreakConstructorInitializersBeforeComma: false -BreakAfterJavaFieldAnnotations: false +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon BreakStringLiterals: true ColumnLimit: 140 CommentPragmas: '^ IWYU pragma:' -ConstructorInitializerAllOnOneLineOrOnePerLine: true +CompactNamespaces: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: true DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock ExperimentalAutoDetectBinPacking: false -ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] FixNamespaceComments: true -IncludeCategories: +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: - Regex: '^<.*\.h>' Priority: 1 + SortPriority: 0 + CaseSensitive: false - Regex: '^<.*' Priority: 2 + SortPriority: 0 + CaseSensitive: false - Regex: '.*' Priority: 3 + SortPriority: 0 + CaseSensitive: false IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false IndentCaseLabels: true +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true IndentWidth: 4 IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false +KeepEmptyLinesAtEOF: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None +ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: false +PackConstructorInitializers: NextLine +PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer ReflowComments: true -SortIncludes: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: Never SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false SpaceBeforeParens: ControlStatements -SpaceInEmptyParentheses: false +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false SpacesBeforeTrailingComments: 2 -SpacesInAngles: false +SpacesInAngles: Never SpacesInContainerLiterals: true -SpacesInCStyleCastParentheses: false -SpacesInParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false SpacesInSquareBrackets: false -SortUsingDeclarations: false Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION TabWidth: 4 UseTab: Always +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE ... diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index df4f70149..11e83a3f9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,7 +47,7 @@ jobs: build: strategy: matrix: - os: [ubuntu-20.04, ubuntu-22.04, macos-latest] + os: [ubuntu-20.04, ubuntu-22.04, macos-13] include: - os: ubuntu-latest sanitizer: ASAN @@ -91,7 +91,7 @@ jobs: make -j4 STRIP=/bin/true cpack - name: 'C++ tests' - if: ${{ matrix.os == 'macos-latest' }} + if: ${{ matrix.os == 'macos-13' }} run: | echo "Running C++ directly in this job due to Action's problem with artifacts transition for macos-11 and macos-12 runners" cd build @@ -111,7 +111,7 @@ jobs: test: strategy: matrix: - os: [ubuntu-20.04, ubuntu-22.04, macos-latest] + os: [ubuntu-20.04, ubuntu-22.04, macos-13] test: ['C++', 'GO'] include: - os: ubuntu-latest @@ -141,18 +141,24 @@ jobs: SANITIZER: ${{matrix.sanitizer}} steps: - name: Checkout repository - if: ${{ matrix.os != 'macos-latest' || matrix.test == 'GO' }} + if: ${{ matrix.os != 'macos-13' || matrix.test == 'GO' }} uses: actions/checkout@v4 + - name: Install Go + uses: actions/setup-go@v5 + if: ${{ matrix.os == 'macos-13' && matrix.test == 'GO' }} + with: + go-version: '1.22.x' + check-latest: true - name: Download ${{matrix.os}}${{matrix.sanitizer}} Artifacts - if: ${{ matrix.os != 'macos-latest' || matrix.test == 'GO' }} + if: ${{ matrix.os != 'macos-13' || matrix.test == 'GO' }} uses: actions/download-artifact@v4 with: name: ${{matrix.os}}${{matrix.sanitizer}} - name: 'Untar Artifacts' - if: ${{ matrix.os != 'macos-latest' || matrix.test == 'GO' }} + if: ${{ matrix.os != 'macos-13' || matrix.test == 'GO' }} run: tar -xvf artifacts.tar - name: Prepare Environment - if: ${{ matrix.os != 'macos-latest' || matrix.test == 'GO' }} + if: ${{ matrix.os != 'macos-13' || matrix.test == 'GO' }} env: OS: ${{matrix.os}} run: | @@ -167,7 +173,7 @@ jobs: ./.github/workflows/install_gtest_parallel.sh fi - name: Tests - if: ${{ matrix.os != 'macos-latest' || matrix.test == 'GO' }} + if: ${{ matrix.os != 'macos-13' || matrix.test == 'GO' }} run: | if [[ $TEST == 'GO' ]]; then if [[ $SANITIZER == 'ASAN' ]]; then diff --git a/bindings.go b/bindings.go index e35ec237d..dbb7c8316 100644 --- a/bindings.go +++ b/bindings.go @@ -499,6 +499,10 @@ func (db *reindexerImpl) resetCaches() { db.resetCachesCtx(context.Background()) } +func WithMaxUpdatesSize(maxUpdatesSizeBytes uint) interface{} { + return bindings.OptionBuiltinMaxUpdatesSize{MaxUpdatesSizeBytes: maxUpdatesSizeBytes} +} + func WithCgoLimit(cgoLimit int) interface{} { return bindings.OptionCgoLimit{CgoLimit: cgoLimit} } diff --git a/bindings/builtin/builtin.go b/bindings/builtin/builtin.go index 4e8c3de6f..303dc94f7 100644 --- a/bindings/builtin/builtin.go +++ b/bindings/builtin/builtin.go @@ -7,11 +7,13 @@ import "C" import ( "context" "encoding/json" + "errors" "fmt" "net/url" "os" "reflect" "runtime" + "strings" "sync" "time" "unsafe" @@ -49,6 +51,7 @@ type Builtin struct { cgoLimiterStat *cgoLimiterStat rx C.uintptr_t ctxWatcher *CtxWatcher + eventsHandler *CGOEventsHandler } type RawCBuffer struct { @@ -170,7 +173,7 @@ func bool2cint(v bool) C.int { return 0 } -func (binding *Builtin) Init(u []url.URL, options ...interface{}) error { +func (binding *Builtin) Init(u []url.URL, eh bindings.EventsHandler, options ...interface{}) error { if binding.rx != 0 { return bindings.NewError("already initialized", bindings.ErrConflict) } @@ -178,6 +181,7 @@ func (binding *Builtin) Init(u []url.URL, options ...interface{}) error { ctxWatchDelay := defCtxWatchDelay ctxWatchersPoolSize := defWatchersPoolSize cgoLimit := defCgoLimit + var maxUpdatesSize uint var rx uintptr var builtinAllocatorConfig *bindings.OptionBuiltinAllocatorConfig connectOptions := *bindings.DefaultConnectOptions() @@ -205,16 +209,21 @@ func (binding *Builtin) Init(u []url.URL, options ...interface{}) error { builtinAllocatorConfig = &v case bindings.ConnectOptions: connectOptions = v + case bindings.OptionBuiltinMaxUpdatesSize: + maxUpdatesSize = v.MaxUpdatesSizeBytes default: fmt.Printf("Unknown builtin option: %#v\n", option) } } if rx == 0 { + subDBName := u[0].Path[strings.LastIndex(u[0].Path, "/")+1:] if builtinAllocatorConfig != nil { rxConfig := C.reindexer_config{ allocator_cache_limit: C.int64_t(builtinAllocatorConfig.AllocatorCacheLimit), allocator_max_cache_part: C.float(builtinAllocatorConfig.AllocatorMaxCachePart), + max_updates_size: C.uint64_t(maxUpdatesSize), + sub_db_name: str2c(subDBName), } binding.rx = C.init_reindexer_with_config(rxConfig) } else { @@ -231,6 +240,7 @@ func (binding *Builtin) Init(u []url.URL, options ...interface{}) error { } binding.ctxWatcher = NewCtxWatcher(ctxWatchersPoolSize, ctxWatchDelay) + binding.eventsHandler = NewCGOEventsHandler(eh) opts := C.ConnectOpts{ storage: C.uint16_t(connectOptions.Storage), @@ -618,10 +628,6 @@ func (binding *Builtin) UpdateQuery(ctx context.Context, data []byte) (bindings. return ret2go(C.reindexer_update_query(binding.rx, buf2c(data), ctxInfo.cCtx)) } -func (binding *Builtin) Commit(ctx context.Context, namespace string) error { - return err2go(C.reindexer_commit(binding.rx, str2c(namespace))) -} - // CGoLogger logger function for C // //export CGoLogger @@ -668,6 +674,9 @@ func (binding *Builtin) ReopenLogFiles() error { } func (binding *Builtin) Finalize() error { + if binding.eventsHandler != nil && binding.rx != 0 { + binding.eventsHandler.Unsubscribe(binding.rx) + } C.destroy_reindexer(binding.rx) binding.rx = 0 if binding.cgoLimiterStat != nil { @@ -693,6 +702,20 @@ func (binding *Builtin) GetDSNs() []url.URL { return nil } +func (binding *Builtin) Subscribe(ctx context.Context, opts *bindings.SubscriptionOptions) error { + if binding.eventsHandler == nil { + return errors.New("Builtin events handler is not initialized") + } + return binding.eventsHandler.Subscribe(binding.rx, opts) +} + +func (binding *Builtin) Unsubscribe(ctx context.Context) error { + if binding.eventsHandler == nil { + return errors.New("Builtin events handler is not initialized") + } + return binding.eventsHandler.Unsubscribe(binding.rx) +} + func newBufFreeBatcher() (bf *bufFreeBatcher) { bf = &bufFreeBatcher{ bufs: make([]*RawCBuffer, 0, 100), diff --git a/bindings/builtin/cgoeventshandler.go b/bindings/builtin/cgoeventshandler.go new file mode 100644 index 000000000..2906a9142 --- /dev/null +++ b/bindings/builtin/cgoeventshandler.go @@ -0,0 +1,131 @@ +package builtin + +// #include "core/cbinding/reindexer_c.h" +// #include "reindexer_cgo.h" +// #include +import "C" + +import ( + "encoding/json" + "sync" + "sync/atomic" + "time" + "unsafe" + + "github.com/restream/reindexer/v4/bindings" +) + +type CGOEventsHandler struct { + eventsHander bindings.EventsHandler + wg sync.WaitGroup + mtx sync.Mutex + terminate int32 + hasReadingRoutine bool +} + +func NewCGOEventsHandler(eh bindings.EventsHandler) *CGOEventsHandler { + if eh != nil { + handler := CGOEventsHandler{eventsHander: eh} + return &handler + } + return nil +} + +func (h *CGOEventsHandler) Subscribe(rx C.uintptr_t, opts *bindings.SubscriptionOptions) error { + bEventsSubOpts, err := json.Marshal(opts) + if err != nil { + return err + } + sEventsSubOpts := string(bEventsSubOpts) + + h.mtx.Lock() + defer h.mtx.Unlock() + + err = err2go(C.reindexer_subscribe(rx, str2c(sEventsSubOpts))) + if err == nil && !h.hasReadingRoutine { + h.wg.Add(1) + go h.eventsReadingRoutine(rx) + h.hasReadingRoutine = true + } + return err +} + +func (h *CGOEventsHandler) Unsubscribe(rx C.uintptr_t) error { + h.mtx.Lock() + defer h.mtx.Unlock() + atomic.StoreInt32(&h.terminate, 1) + err := err2go(C.reindexer_unsubscribe(rx)) + h.wg.Wait() + atomic.StoreInt32(&h.terminate, 0) + h.hasReadingRoutine = false + return err +} + +type RawStackCBuffer struct { + cbuf C.reindexer_buffer +} + +func (buf *RawStackCBuffer) Free() { +} + +func (buf *RawStackCBuffer) GetBuf() []byte { + if buf.cbuf.data == nil || buf.cbuf.len == 0 { + return nil + } + length := int(buf.cbuf.len) + return (*[1 << 30]byte)(unsafe.Pointer(buf.cbuf.data))[:length:length] +} + +// Converts C-array into Go buffer. C-array will NOT be deallocated automatically +func arr_ret2go_static(ret C.reindexer_array_ret) ([]RawStackCBuffer, error) { + if ret.err_code != 0 { + defer C.free(unsafe.Pointer(uintptr(ret.data))) + if err := ctxErr(int(ret.err_code)); err != nil { + return nil, err + } + return nil, bindings.NewError("rq:"+C.GoString((*C.char)(unsafe.Pointer(uintptr(ret.data)))), int(ret.err_code)) + } + + sz := uint32(ret.out_size) + if ret.out_buffers == nil || sz == 0 { + return nil, nil + } + cslice := (*[1 << 30]C.reindexer_buffer)(unsafe.Pointer(ret.out_buffers))[:sz:sz] + rbufs := make([]RawStackCBuffer, len(cslice)) + for i := 0; i < len(cslice); i++ { + rbufs[i].cbuf = cslice[i] + } + return rbufs, nil +} + +func (h *CGOEventsHandler) eventsReadingRoutine(rx C.uintptr_t) { + defer h.wg.Done() + + const kEventsCbufsCount = 2048 + var cbufs [kEventsCbufsCount]C.reindexer_buffer + + for { + terminate := atomic.LoadInt32(&h.terminate) + if terminate == 1 { + return + } + bufs, err := arr_ret2go_static(C.reindexer_read_events(rx, &cbufs[0], C.uint32_t(kEventsCbufsCount))) + terminate = atomic.LoadInt32(&h.terminate) + if terminate == 1 { + return + } + if err == nil { + for i := 0; i < len(bufs); i++ { + h.eventsHander.OnEvent(&bufs[i]) + } + if len(bufs) > 0 { + C.reindexer_erase_events(rx, C.uint32_t(len(bufs))) + } + } else { + h.eventsHander.OnError(err) + } + if len(bufs) == 0 { + time.Sleep(25 * time.Millisecond) + } + } +} diff --git a/bindings/builtinserver/builtinserver.go b/bindings/builtinserver/builtinserver.go index 811daf6a2..9451b27f1 100644 --- a/bindings/builtinserver/builtinserver.go +++ b/bindings/builtinserver/builtinserver.go @@ -67,7 +67,7 @@ func (server *BuiltinServer) stopServer(timeout time.Duration) error { } } -func (server *BuiltinServer) Init(u []url.URL, options ...interface{}) error { +func (server *BuiltinServer) Init(u []url.URL, eh bindings.EventsHandler, options ...interface{}) error { if server.builtin != nil { return bindings.NewError("already initialized", bindings.ErrConflict) } @@ -133,7 +133,7 @@ func (server *BuiltinServer) Init(u []url.URL, options ...interface{}) error { builtinURL[0].Path = "" options = append(options, bindings.OptionReindexerInstance{Instance: uintptr(rx)}) - return server.builtin.Init(builtinURL, options...) + return server.builtin.Init(builtinURL, eh, options...) } func (server *BuiltinServer) Clone() bindings.RawBinding { @@ -241,10 +241,6 @@ func (server *BuiltinServer) UpdateQuery(ctx context.Context, rawQuery []byte) ( return server.builtin.UpdateQuery(ctx, rawQuery) } -func (server *BuiltinServer) Commit(ctx context.Context, namespace string) error { - return server.builtin.Commit(ctx, namespace) -} - func (server *BuiltinServer) EnableLogger(logger bindings.Logger) { server.builtin.EnableLogger(logger) } @@ -279,6 +275,14 @@ func (server *BuiltinServer) Ping(ctx context.Context) error { return server.builtin.Ping(ctx) } -func (binding *BuiltinServer) GetDSNs() []url.URL { +func (server *BuiltinServer) GetDSNs() []url.URL { return nil } + +func (server *BuiltinServer) Subscribe(ctx context.Context, opts *bindings.SubscriptionOptions) error { + return server.builtin.Subscribe(ctx, opts) +} + +func (server *BuiltinServer) Unsubscribe(ctx context.Context) error { + return server.builtin.Unsubscribe(ctx) +} diff --git a/bindings/builtinserver/config/config.go b/bindings/builtinserver/config/config.go index 063e1eef9..33b7ddb8b 100644 --- a/bindings/builtinserver/config/config.go +++ b/bindings/builtinserver/config/config.go @@ -27,6 +27,7 @@ type NetConf struct { Security bool `yaml:"security"` HttpReadTimeoutSec int `yaml:"http_read_timeout,omitempty"` HttpWriteTimeoutSec int `yaml:"http_write_timeout,omitempty"` + MaxUpdatesSizeBytes uint `yaml:"maxupdatessize,omitempty"` } type LoggerConf struct { diff --git a/bindings/consts.go b/bindings/consts.go index 9b680d071..a3f188358 100644 --- a/bindings/consts.go +++ b/bindings/consts.go @@ -2,7 +2,7 @@ package bindings const CInt32Max = int(^uint32(0) >> 1) -const ReindexerVersion = "v4.15.0" +const ReindexerVersion = "v4.16.0" // public go consts from type_consts.h and reindexer_ctypes.h const ( diff --git a/bindings/cproto/connection.go b/bindings/cproto/connection.go index 806fd4c00..202925dc8 100644 --- a/bindings/cproto/connection.go +++ b/bindings/cproto/connection.go @@ -63,7 +63,7 @@ const ( cmdStartTransaction = 29 cmdDeleteQueryTx = 30 cmdUpdateQueryTx = 31 - cmdCommit = 32 + cmdCommit = 32 // DEPRECATED cmdModifyItem = 33 cmdDeleteQuery = 34 cmdUpdateQuery = 35 @@ -76,17 +76,19 @@ const ( cmdPutMeta = 65 cmdEnumMeta = 66 cmdSetSchema = 67 + cmdSubscribe = 90 + cmdEvent = 91 cmdCodeMax = 128 ) type connFactory interface { - newConnection(ctx context.Context, params newConnParams, loggerOwner LoggerOwner) (connection, int64, error) + newConnection(ctx context.Context, params newConnParams, loggerOwner LoggerOwner, eventsHandler bindings.EventsHandler) (connection, int64, error) } type connFactoryImpl struct{} -func (cf *connFactoryImpl) newConnection(ctx context.Context, params newConnParams, loggerOwner LoggerOwner) (connection, int64, error) { - return newConnection(ctx, params, loggerOwner) +func (cf *connFactoryImpl) newConnection(ctx context.Context, params newConnParams, loggerOwner LoggerOwner, eventsHandler bindings.EventsHandler) (connection, int64, error) { + return newConnection(ctx, params, loggerOwner, eventsHandler) } type requestInfo struct { @@ -139,6 +141,7 @@ type connectionImpl struct { enableCompression bool requestDedicatedThread bool loggerOwner LoggerOwner + eventsHandler bindings.EventsHandler } type newConnParams struct { @@ -150,11 +153,12 @@ type newConnParams struct { enableCompression bool requestDedicatedThread bool caps bindings.BindingCapabilities + envetsHandler bindings.EventsHandler } func newConnection( ctx context.Context, - params newConnParams, loggerOwner LoggerOwner) ( + params newConnParams, loggerOwner LoggerOwner, eventsHandler bindings.EventsHandler) ( connection, int64, error, @@ -170,6 +174,7 @@ func newConnection( enableCompression: params.enableCompression, requestDedicatedThread: params.requestDedicatedThread, loggerOwner: loggerOwner, + eventsHandler: eventsHandler, } for i := 0; i < queueSize; i++ { c.seqs <- uint32(i) @@ -198,10 +203,7 @@ func newConnection( } func seqNumIsValid(seqNum uint32) bool { - if seqNum < maxSeqNum { - return true - } - return false + return seqNum < maxSeqNum } func (c *connectionImpl) logMsg(level int, fmt string, msg ...interface{}) { @@ -317,6 +319,7 @@ func (c *connectionImpl) readReply(hdr []byte) (err error) { if _, err = io.ReadFull(c.rdBuf, hdr); err != nil { return } + ser := cjson.NewSerializer(hdr) magic := ser.GetUInt32() @@ -344,6 +347,28 @@ func (c *connectionImpl) readReply(hdr []byte) (err error) { if !seqNumIsValid(rseq) { return fmt.Errorf("invalid seq num: %d", rseq) } + + if cmd == cmdEvent { + answ := newNetBuffer(size, c) + if _, err = io.ReadFull(c.rdBuf, answ.buf); err != nil { + return + } + if compressed { + answ.decompress() + } + if c.eventsHandler != nil { + if err = answ.parseArgs(); err != nil { + return + } + if err = c.eventsHandler.OnEvent(answ); err != nil { + return + } + } else { + c.logMsg(1, "rq: got event message on the connection, which should be not subscribed to the events. Seq number: %v\n", rseq) + } + return + } + reqID := rseq % queueSize if atomic.LoadUint32(&c.requests[reqID].seqNum) != rseq { if needCancelAnswer(cmd) { @@ -664,6 +689,10 @@ func (c *connectionImpl) onError(err error) { } } } + + if c.eventsHandler != nil { + c.eventsHandler.OnError(err) + } } c.lock.Unlock() } diff --git a/bindings/cproto/connection.mock.go b/bindings/cproto/connection.mock.go index 66954b4e2..6e5a6d1f5 100644 --- a/bindings/cproto/connection.mock.go +++ b/bindings/cproto/connection.mock.go @@ -58,7 +58,7 @@ func NewMockConnection(t *testing.T) *MockConnection { return mock } -func (mcf *MockConnFactory) newConnection(ctx context.Context, params newConnParams, loggerOwner LoggerOwner) (connection, int64, error) { +func (mcf *MockConnFactory) newConnection(ctx context.Context, params newConnParams, loggerOwner LoggerOwner, eh bindings.EventsHandler) (connection, int64, error) { c, ok := mcf.expCalls["newConnection"] if !ok { mcf.t.Fatalf("unexpected call newConnection") diff --git a/bindings/cproto/cproto.go b/bindings/cproto/cproto.go index a91f68399..690945d67 100644 --- a/bindings/cproto/cproto.go +++ b/bindings/cproto/cproto.go @@ -83,6 +83,7 @@ type NetCProto struct { dedicatedThreads bindings.OptionDedicatedThreads caps bindings.BindingCapabilities appName string + eventsHandler bindings.EventsHandler termCh chan struct{} lock sync.RWMutex logger bindings.Logger @@ -267,7 +268,7 @@ func (binding *NetCProto) GetDSNs() []url.URL { return binding.dsn.urls } -func (binding *NetCProto) Init(u []url.URL, options ...interface{}) (err error) { +func (binding *NetCProto) Init(u []url.URL, eh bindings.EventsHandler, options ...interface{}) (err error) { connPoolSize := defConnPoolSize connPoolLBAlgorithm := defConnPoolLBAlgorithm binding.appName = defAppName @@ -326,6 +327,7 @@ func (binding *NetCProto) Init(u []url.URL, options ...interface{}) (err error) binding.retryAttempts.Write = 0 } + binding.eventsHandler = eh binding.dsn.connFactory = &connFactoryImpl{} binding.dsn.urls = u for i := 0; i < len(binding.dsn.urls); i++ { @@ -346,48 +348,38 @@ func (binding *NetCProto) Init(u []url.URL, options ...interface{}) (err error) func (binding *NetCProto) newPool(ctx context.Context, connPoolSize int, connPoolLBAlgorithm bindings.LoadBalancingAlgorithm) error { var wg sync.WaitGroup - for _, conn := range binding.pool.conns { + for _, conn := range binding.pool.sharedConns { conn.finalize() } + if binding.pool.eventsConn != nil { + binding.pool.eventsConn.finalize() + } binding.pool = pool{ - conns: make([]connection, connPoolSize), + sharedConns: make([]connection, connPoolSize), lbAlgorithm: connPoolLBAlgorithm, } + connParams := binding.createConnParams() + wg.Add(connPoolSize) for i := 0; i < connPoolSize; i++ { go func(binding *NetCProto, wg *sync.WaitGroup, i int) { defer wg.Done() - conn, serverStartTS, _ := binding.dsn.connFactory.newConnection( - ctx, - newConnParams{ - dsn: binding.getActiveDSN(), - loginTimeout: binding.timeouts.LoginTimeout, - requestTimeout: binding.timeouts.RequestTimeout, - createDBIfMissing: binding.connectOpts.CreateDBIfMissing, - appName: binding.appName, - enableCompression: binding.compression.EnableCompression, - requestDedicatedThread: binding.dedicatedThreads.DedicatedThreads, - caps: binding.caps, - }, - binding, - ) - + conn, serverStartTS, _ := binding.dsn.connFactory.newConnection(ctx, connParams, binding, nil) if serverStartTS > 0 { old := atomic.SwapInt64(&binding.serverStartTime, serverStartTS) if old != 0 && old != serverStartTS { atomic.StoreInt32(&binding.isServerChanged, 1) } } - - binding.pool.conns[i] = conn + binding.pool.sharedConns[i] = conn }(binding, &wg, i) } wg.Wait() - for _, conn := range binding.pool.conns { + for _, conn := range binding.pool.sharedConns { if conn.hasError() { return conn.curError() } @@ -396,6 +388,36 @@ func (binding *NetCProto) newPool(ctx context.Context, connPoolSize int, connPoo return nil } +func (binding *NetCProto) createConnParams() newConnParams { + return newConnParams{dsn: binding.getActiveDSN(), + loginTimeout: binding.timeouts.LoginTimeout, + requestTimeout: binding.timeouts.RequestTimeout, + createDBIfMissing: binding.connectOpts.CreateDBIfMissing, + appName: binding.appName, + enableCompression: binding.compression.EnableCompression, + requestDedicatedThread: binding.dedicatedThreads.DedicatedThreads, + caps: binding.caps} +} + +func (binding *NetCProto) createEventConn(ctx context.Context, connParams newConnParams, eventsSubOptsJSON []byte) error { + conn, serverStartTS, err := binding.dsn.connFactory.newConnection(ctx, connParams, binding, binding.eventsHandler) + if serverStartTS > 0 { + old := atomic.SwapInt64(&binding.serverStartTime, serverStartTS) + if old != 0 && old != serverStartTS { + atomic.StoreInt32(&binding.isServerChanged, 1) + } + } + if err != nil { + return err + } + err = conn.rpcCallNoResults(ctx, cmdSubscribe, uint32(binding.timeouts.RequestTimeout/time.Second), 1, []byte{}, 0, eventsSubOptsJSON) + if err != nil { + return err + } + binding.pool.eventsConn = conn + return nil +} + func (binding *NetCProto) Clone() bindings.RawBinding { return &NetCProto{} } @@ -637,10 +659,6 @@ func (binding *NetCProto) UpdateQuery(ctx context.Context, data []byte) (binding return binding.rpcCall(ctx, opWr, cmdUpdateQuery, data) } -func (binding *NetCProto) Commit(ctx context.Context, namespace string) error { - return binding.rpcCallNoResults(ctx, opWr, cmdCommit, namespace) -} - func (binding *NetCProto) OnChangeCallback(f func()) { binding.onChangeCallback = f } @@ -723,10 +741,67 @@ func (binding *NetCProto) Finalize() error { return nil } +func (binding *NetCProto) callSubscribe(ctx context.Context, eventsSubOptsJSON []byte) error { + return binding.pool.eventsConn.rpcCallNoResults(ctx, cmdSubscribe, uint32(binding.timeouts.RequestTimeout/time.Second), 1, []byte{}, 0, eventsSubOptsJSON) +} + +func (binding *NetCProto) tryReuseSubConn(ctx context.Context, eventsSubOptsJSON []byte) (bool, error) { + binding.lock.RLock() + defer binding.lock.RUnlock() + if binding.pool.eventsConn != nil && !binding.pool.eventsConn.hasError() { + return true, binding.callSubscribe(ctx, eventsSubOptsJSON) + } + return false, nil +} + +func (binding *NetCProto) Subscribe(ctx context.Context, opts *bindings.SubscriptionOptions) error { + eventsSubOptsJSON, err := json.Marshal(opts) + if err != nil { + return err + } + + reused, err := binding.tryReuseSubConn(ctx, eventsSubOptsJSON) + if err != nil { + return err + } + if reused { + return nil + } + + binding.lock.Lock() + defer binding.lock.Unlock() + if binding.pool.eventsConn != nil { + if !binding.pool.eventsConn.hasError() { + return binding.callSubscribe(ctx, eventsSubOptsJSON) + } + binding.pool.eventsConn.finalize() + } + return binding.createEventConn(ctx, binding.createConnParams(), eventsSubOptsJSON) +} + +func (binding *NetCProto) unsubscribeImpl(context.Context) (connection, error) { + binding.lock.Lock() + defer binding.lock.Unlock() + if binding.pool.eventsConn == nil { + return nil, errors.New("not subscribed") + } + conn := binding.pool.eventsConn + binding.pool.eventsConn = nil + return conn, nil +} + +func (binding *NetCProto) Unsubscribe(ctx context.Context) error { + if conn, err := binding.unsubscribeImpl(ctx); err != nil { + return err + } else { + return conn.finalize() + } +} + func (binding *NetCProto) getAllConns() []connection { binding.lock.RLock() defer binding.lock.RUnlock() - return binding.pool.conns + return binding.pool.sharedConns } func (binding *NetCProto) logMsg(level int, fmt string, msg ...interface{}) { @@ -770,7 +845,7 @@ func (binding *NetCProto) getConnection(ctx context.Context) (conn connection, e } binding.lock.Lock() if currVersion+1 == binding.dsn.connVersion { - for _, conn := range binding.pool.conns { + for _, conn := range binding.pool.sharedConns { conn.finalize() } binding.logMsg(3, "rq: reconnecting with strategy: %s \n", binding.dsn.reconnectionStrategy) @@ -834,7 +909,7 @@ func (binding *NetCProto) reconnect(ctx context.Context) (conn connection, err e if binding.dsn.connTry < 100 { binding.dsn.connTry++ } - err = binding.connectDSN(ctx, len(binding.pool.conns), binding.pool.lbAlgorithm) + err = binding.connectDSN(ctx, len(binding.pool.sharedConns), binding.pool.lbAlgorithm) if err != nil { time.Sleep(time.Duration(binding.dsn.connTry) * time.Millisecond) } else { diff --git a/bindings/cproto/cproto_test.go b/bindings/cproto/cproto_test.go index 357b35cf4..6c9bb153a 100644 --- a/bindings/cproto/cproto_test.go +++ b/bindings/cproto/cproto_test.go @@ -32,7 +32,7 @@ func BenchmarkGetConn(b *testing.B) { binding := NetCProto{} u, _ := url.Parse(fmt.Sprintf("cproto://127.0.0.1:%s/%s_%s", srv1.RpcPort, srv1.DbName, srv1.RpcPort)) dsn := []url.URL{*u} - err := binding.Init(dsn, bindings.OptionConnect{CreateDBIfMissing: true}) + err := binding.Init(dsn, nil, bindings.OptionConnect{CreateDBIfMissing: true}) if err != nil { panic(err) } @@ -60,7 +60,7 @@ func TestCprotoPool(t *testing.T) { defer serv.Close() c := new(NetCProto) - err = c.Init([]url.URL{*addr}) + err = c.Init([]url.URL{*addr}, nil) require.NoError(t, err) assert.Equal(t, defConnPoolSize, len(serv.conns)) @@ -77,7 +77,7 @@ func TestCprotoPool(t *testing.T) { u, err := url.Parse(dsn) require.NoError(t, err) c := new(NetCProto) - err = c.Init([]url.URL{*u}, reindexer.WithConnPoolLoadBalancing(bindings.LBRoundRobin)) + err = c.Init([]url.URL{*u}, nil, reindexer.WithConnPoolLoadBalancing(bindings.LBRoundRobin)) require.NoError(t, err) conns := make(map[connection]bool) @@ -111,7 +111,7 @@ func TestCprotoStatus(t *testing.T) { u, err := url.Parse(dsn) assert.NoError(t, err) c := new(NetCProto) - err = c.Init([]url.URL{*u}) + err = c.Init([]url.URL{*u}, nil) assert.NoError(t, err) status := c.Status(context.Background()) diff --git a/bindings/cproto/cproto_unit_test.go b/bindings/cproto/cproto_unit_test.go index b19b04066..086e50f4a 100644 --- a/bindings/cproto/cproto_unit_test.go +++ b/bindings/cproto/cproto_unit_test.go @@ -301,7 +301,7 @@ func (fx *fixture) addDSNs(t *testing.T, dsns []url.URL) { for _, u := range dsns { fx.dsn.urls = append(fx.dsn.urls, u) mock := NewMockConnection(t) - fx.pool.conns = append(fx.pool.conns, mock) + fx.pool.sharedConns = append(fx.pool.sharedConns, mock) fx.mockConns = append(fx.mockConns, mock) } } diff --git a/bindings/cproto/pool.go b/bindings/cproto/pool.go index 66da57d71..94f61220a 100644 --- a/bindings/cproto/pool.go +++ b/bindings/cproto/pool.go @@ -8,7 +8,8 @@ import ( ) type pool struct { - conns []connection + sharedConns []connection + eventsConn connection lbAlgorithm bindings.LoadBalancingAlgorithm roundRobinParams struct { next uint64 @@ -32,7 +33,7 @@ func (p *pool) lbRoundRobin() connection { nextP := &p.roundRobinParams.next id := atomic.AddUint64(nextP, 1) - for id >= uint64(len(p.conns)) { + for id >= uint64(len(p.sharedConns)) { if atomic.CompareAndSwapUint64(nextP, id, 0) { id = 0 } else { @@ -40,26 +41,26 @@ func (p *pool) lbRoundRobin() connection { } } - return p.conns[id] + return p.sharedConns[id] } // Load balance connections randomly func (p *pool) lbRandom() connection { - id := rand.Intn(len(p.conns)) + id := rand.Intn(len(p.sharedConns)) - return p.conns[id] + return p.sharedConns[id] } // Load balance connections using "Power of Two Choices" algorithm. // See also: https://www.nginx.com/blog/nginx-power-of-two-choices-load-balancing-algorithm/ func (p *pool) lbPowerOfTwoChoices() connection { - id1 := rand.Intn(len(p.conns)) - conn1 := p.conns[id1] + id1 := rand.Intn(len(p.sharedConns)) + conn1 := p.sharedConns[id1] conn1Seqs := conn1.getSeqs() conn1QueueUsage := cap(conn1Seqs) - len(conn1Seqs) - id2 := rand.Intn(len(p.conns)) - conn2 := p.conns[id2] + id2 := rand.Intn(len(p.sharedConns)) + conn2 := p.sharedConns[id2] conn2Seqs := conn2.getSeqs() conn2QueueUsage := cap(conn2Seqs) - len(conn2Seqs) diff --git a/bindings/interface.go b/bindings/interface.go index 7104612bb..9fa1830fe 100644 --- a/bindings/interface.go +++ b/bindings/interface.go @@ -211,7 +211,7 @@ type Stats struct { // Raw binding to reindexer type RawBinding interface { - Init(u []url.URL, options ...interface{}) error + Init(u []url.URL, eh EventsHandler, options ...interface{}) error Clone() RawBinding OpenNamespace(ctx context.Context, namespace string, enableStorage, dropOnFileFormatError bool) error CloseNamespace(ctx context.Context, namespace string) error @@ -239,7 +239,6 @@ type RawBinding interface { SelectQuery(ctx context.Context, rawQuery []byte, asJson bool, ptVersions []int32, fetchCount int) (RawBuffer, error) DeleteQuery(ctx context.Context, rawQuery []byte) (RawBuffer, error) UpdateQuery(ctx context.Context, rawQuery []byte) (RawBuffer, error) - Commit(ctx context.Context, namespace string) error EnableLogger(logger Logger) DisableLogger() GetLogger() Logger @@ -248,6 +247,8 @@ type RawBinding interface { Finalize() error Status(ctx context.Context) Status GetDSNs() []url.URL + Subscribe(ctx context.Context, opts *SubscriptionOptions) error + Unsubscribe(ctx context.Context) error } type RawBindingChanging interface { @@ -281,6 +282,11 @@ type OptionBuiltinAllocatorConfig struct { AllocatorMaxCachePart float32 } +// MaxUpdatesSizeBytes for the internal replication/events queues +type OptionBuiltinMaxUpdatesSize struct { + MaxUpdatesSizeBytes uint +} + type OptionCgoLimit struct { CgoLimit int } diff --git a/bindings/subscription.go b/bindings/subscription.go new file mode 100644 index 000000000..f04d8ad85 --- /dev/null +++ b/bindings/subscription.go @@ -0,0 +1,173 @@ +package bindings + +import ( + "bytes" + "encoding/json" + "time" +) + +// Operation counter and server id +type LsnT struct { + // Operation counter + Counter int64 `json:"counter"` + // Node identifier + ServerId int `json:"server_id"` +} + +const kLSNCounterBitsCount = 48 +const kLSNCounterMask int64 = (int64(1) << kLSNCounterBitsCount) - int64(1) +const kLSNDigitCountMult int64 = 1000000000000000 + +func (lsn *LsnT) IsCompatibleWith(o LsnT) bool { + return lsn.ServerId == o.ServerId +} + +func (lsn *LsnT) IsNewerThen(o LsnT) bool { + return lsn.Counter > o.Counter +} + +func (lsn *LsnT) IsEmpty() bool { return lsn.Counter == kLSNDigitCountMult-1 } + +func CreateLSNFromInt64(v int64) LsnT { + if (v & kLSNCounterMask) == kLSNCounterMask { + return LsnT{Counter: kLSNDigitCountMult - 1, ServerId: -1} + } + + server := v / kLSNDigitCountMult + return LsnT{Counter: v - server*kLSNDigitCountMult, ServerId: int(server)} +} + +func CreateInt64FromLSN(v LsnT) int64 { + return int64(v.ServerId)*kLSNDigitCountMult + v.Counter +} + +func CreateEmptyLSN() LsnT { + return LsnT{Counter: kLSNDigitCountMult - 1, ServerId: 0} +} + +const ( + EventTypeNone = 0 + EventTypeItemUpdate = 1 + EventTypeItemUpsert = 2 + EventTypeItemDelete = 3 + EventTypeItemInsert = 4 + EventTypeItemUpdateTx = 5 + EventTypeItemUpsertTx = 6 + EventTypeItemDeleteTx = 7 + EventTypeItemInsertTx = 8 + EventTypeIndexAdd = 9 + EventTypeIndexDrop = 10 + EventTypeIndexUpdate = 11 + EventTypePutMeta = 12 + EventTypePutMetaTx = 13 + EventTypeUpdateQuery = 14 + EventTypeDeleteQuery = 15 + EventTypeUpdateQueryTx = 16 + EventTypeDeleteQueryTx = 17 + EventTypeSetSchema = 18 + EventTypeTruncate = 19 + EventTypeBeginTx = 20 + EventTypeCommitTx = 21 + EventTypeAddNamespace = 22 + EventTypeDropNamespace = 23 + EventTypeCloseNamespace = 24 + EventTypeRenameNamespace = 25 + EventTypeResyncNamespaceGeneric = 26 + EventTypeResyncNamespaceLeaderInit = 27 + EventTypeResyncOnUpdatesDrop = 28 + EventTypeEmptyUpdate = 29 + EventTypeNodeNetworkCheck = 30 + EventTypeSetTagsMatcher = 31 + EventTypeSetTagsMatcherTx = 32 + EventTypeSaveShardingConfig = 33 + EventTypeApplyShardingConfig = 34 + EventTypeResetOldShardingConfig = 35 + EventTypeResetCandidateConfig = 36 + EventTypeRollbackCandidateConfig = 37 + EventTypeDeleteMeta = 38 +) + +// Events handler interface +type Event struct { + Type int + NSVersion LsnT + LSN LsnT + ShardID int + ServerID int + Database string + Namespace string + Timestamp time.Time +} + +const ( + SubscriptionTypeNamespaces = 0 + SubscriptionTypeDatabase = 1 +) + +type EventsNamespaceFilters struct { + Name string `json:"name"` + // TODO: No filters yet +} + +type EventsStreamOptions struct { + Namespaces []EventsNamespaceFilters `json:"namespaces"` + WithConfigNamespace bool `json:"with_config_namespace"` + EventTypes EventTypesMap `json:"event_types"` +} + +type EventsStreamOptionsByStream struct { + *EventsStreamOptions + StreamID int `json:"id"` +} + +const ( + SubscriptionDataTypeNone = "none" +) + +type EventTypesMap map[string]struct{} + +type SubscriptionOptions struct { + Version int `json:"version"` + SubscriptionType int `json:"subscription_type"` + Streams []EventsStreamOptionsByStream `json:"streams"` + WithDBName bool `json:"with_db_name"` + WithServerID bool `json:"with_server_id"` + WithShardID bool `json:"with_shard_id"` + WithLSN bool `json:"with_lsn"` + WithTimestamp bool `json:"with_timestamp"` + DataType string `json:"data_type"` +} + +func (em EventTypesMap) MarshalJSON() ([]byte, error) { + out := bytes.NewBuffer(make([]byte, 0, 1024)) + out.WriteByte('[') + i := 0 + for typ := range em { + if i != 0 { + out.WriteByte(',') + } + i++ + out.WriteByte('"') + out.WriteString(typ) + out.WriteByte('"') + } + out.WriteByte(']') + return out.Bytes(), nil +} + +func (em EventTypesMap) UnmarshalJSON(b []byte) error { + em = make(EventTypesMap) + var slice []string + if err := json.Unmarshal(b, &slice); err != nil { + return err + } + for _, typ := range slice { + em[typ] = struct{}{} + } + return nil +} + +type EventsHandler interface { + OnEvent(RawBuffer) error + OnError(error) error +} diff --git a/changelog.md b/changelog.md index c679500a9..01eeec5c7 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,36 @@ +# Version 4.16.0 *beta* (26.07.2024) + +## Reindexer server +- [fea] Added RPC API for updates subcription + +## Go connector +- [fea] Added database [events subscription](readme.md#events-subscription) + +## Sharding +- [fea] Improved errors handling in proxied transactions + +## Ported +- [fea/fix] Ported all the fixes and features from [v3.25.0](https://github.com/Restream/reindexer/releases/tag/v3.25.0), [v3.26.0](https://github.com/Restream/reindexer/releases/tag/v3.26.0) and [v3.27.0](https://github.com/Restream/reindexer/releases/tag/v3.27.0) + +## Deploy +- [fea] Added build for Ubuntu 24.04 +- [ref] CentOS 7 reindexer repo is no longer supported due to CentOS 7 EOL + +## Face +- [fea] Increased max allowed value of the `position_boost` field +- [fea] Added "Minimum preselect size for optimization of inner join by injection of filters" field to NS config +- [fea] Added `UUID` index type to PK options +- [fix] Fixed the issue related to Aggregation result displaying +- [fix] Fixed the pagination issue +- [fix] Fixed the console issue in Meta section +- [fix] Fixed Explain tab layout on SQL result page +- [fix] Fixed placeholder layout for "Forced sort values" field and filer condition on Query Builder page +- [fix] Fixed icons and tabs layout on NS page +- [fix] Fixed "Tags" input layout in Index config modal +- [fix] Fixed default value for array fields +- [fix] Fixed tabs layout on NS page after Explain request +- [fix] Fixed input width in the settings + # Version 4.15.0 *beta* (22.04.2024) ## Core @@ -492,7 +525,7 @@ Storages for v3 and v4 are compatible in both ways. - [fix] Fixed indexes for client stats - [fix] Fixed optimization cancelling for concurrent queries - [fix] Do not rebuild composite indexes after update -- [fix] Removed TSAN suppressions for tests +- [fix] Removed TSAN suppression for tests ## Face - [fea] Added tooltips to Grid columns @@ -505,7 +538,7 @@ Storages for v3 and v4 are compatible in both ways. - [fix] Fixed the issue with losing a namespace focus during tabs changing - [fix] Performed yarn upgrade - [fix] Fixed the issue with the sorting params keeping -- [fix] Fixed the issue with case sensitive field names during the grid building +- [fix] Fixed the issue with case-sensitive field names during the grid building - [fix] Fixed the issue with slow 3g in the Namespace list - [fix] Fixed the "Default stop words" option on the "Add index" form - [fix] Fixed the issue with the full-text config and full-text synonyms definition config areas on the "Add index" form @@ -517,7 +550,7 @@ Storages for v3 and v4 are compatible in both ways. # Version 3.2.2 (16.07.2021) ## Core - [fea] Optimize string refs counting for wide-range queries -- [fix] Fix merge limit handling for deleted values in fultext index +- [fix] Fix merge limit handling for deleted values in fulltext index - [fix] Fix cascade replication for nodes without storage - [fix] Fix sorted indexes update @@ -582,7 +615,7 @@ Storages for v3 and v4 are compatible in both ways. ## Core - [fix] Fixed segfault in fulltext query with brackets -- [fix] Fixed deadlock in selecter in case of concurrent namespace removing +- [fix] Fixed deadlock in selector in case of concurrent namespace removing - [fix] Fixed true/false tokens parsing inside query to composite index ## Reindexer server @@ -614,11 +647,11 @@ Storages for v3 and v4 are compatible in both ways. # Version 3.1.1 (29.03.2021) ## Core - [fix] Bug in full text query with single mandatory word fixed -- [fix] Bug in query with condition ALLSET by nonindexed field fixed +- [fix] Bug in query with condition ALLSET by non indexed field fixed - [fix] Bug in query with merge and join by the same namespace fixed - [fix] Simultaneous update of field and whole object fixed - [fix] Build on aarch64 architecture fixed -- [fix] Fixed replication updates limit trackig, and possible inifity full namespace sync +- [fix] Fixed replication updates limit tracking, and possible infinity full namespace sync - [fix] Fixed web face page corruption on Windows builds ## Reindexer server @@ -639,7 +672,7 @@ Storages for v3 and v4 are compatible in both ways. - [fix] Mandatory terms with multiword synonyms in fulltext queries fixed - [fea] Verification of EQUAL_POSITION by the same field added - [fea] Added new syntax for update of array's elements -- [fea] Impoved verification of fulltext index configuration +- [fea] Improved verification of fulltext index configuration ## Reindexer server - [fea] api/v1/check returns more information @@ -673,7 +706,7 @@ Storages for v3 and v4 are compatible in both ways. - [fix] Build builtin/builtinserver on mingw ## Face -- [fea] Added tooltips to longest query +- [fea] Added tooltips to the longest query - [fix] Fixed the query view on the Query -> SQL page - [fix] Added checking for unsaved data during the window closing - [fix] Bug with the pagination in the List mode @@ -698,7 +731,7 @@ Storages for v3 and v4 are compatible in both ways. # Version 3.0.1 (31.12.2020) ## Core -- [fix] Search by multi word synonyms is fixed +- [fix] Search by multi-word synonyms is fixed - [fix] Comparator performance issue of condition IN (many strings) ## Face @@ -746,7 +779,7 @@ Storages for v3 and v4 are compatible in both ways. - [fix] Fix outdated namespace removing from prometheus stats ## Reindexer tool -- [fix] Fix command execution iterrupts on SIGINT +- [fix] Fix command execution interrupts on SIGINT - [fix] Disable replicator for reindexer_tool ## Go connector @@ -759,8 +792,8 @@ Storages for v3 and v4 are compatible in both ways. - [fea] Added extra parameter to clients stats - [fea] Added update, delete, truncate statement in DSL - [fix] Added support for equal_positions in sql suggester -- [fix] Crash on distinct whith composite index -- [fix] Crash on query whith incorrect index type after index conversion +- [fix] Crash on distinct with composite index +- [fix] Crash on query with incorrect index type after index conversion ## Reindexer tool - [fix] Crash on upsert array object as first json tag @@ -932,7 +965,7 @@ Storages for v3 and v4 are compatible in both ways. - [fea] Improved behavior while input is redirected # Go connector -- [fix] Enable to create multiple instances of builtinserver +- [fix] Enable to create multiple instances of built-in server - [fea] Multiple dsn support in cproto # Face @@ -1016,7 +1049,7 @@ Storages for v3 and v4 are compatible in both ways. - [fix] Select fields filter fix for right namespace # Reindexer server -- [fea] web static resources are embeded to server binary by default +- [fea] web static resources are embedded to server binary by default # Version 2.5.5 (07.02.2020) @@ -1075,7 +1108,7 @@ Storages for v3 and v4 are compatible in both ways. - [fix] Fix assert in sort by composite indexes - [fea] Add composite values parsing for SQL select - [fix] Make circular accumulator for stddev performance statistic -- [fix] Fix unhandled exception while caclulating perf stat +- [fix] Fix unhandled exception while calculating perf stat ## go connector - [fix] RawBuffer leak due to unclosed iterators in transactions @@ -1108,7 +1141,7 @@ Storages for v3 and v4 are compatible in both ways. ## Core - [fea] Sort by expressions -- [fea] Optimized lock time for joins with small preresult set +- [fea] Optimized lock time for joins with small pre-result set - [fea] Added more info about replication state to #memstat namespace - [fix] LSN on row-based query replication (possible assert on server startup) - [fix] Replication clusterID for namespaces without storage @@ -1146,13 +1179,13 @@ Storages for v3 and v4 are compatible in both ways. - [fea] Cancelling queries execution by Ctrl+C ## go connector -- [fea] Iterator.NextObj() unmarshals data to any user provided struct +- [fea] Iterator.NextObj() unmarshal data to any user provided struct # Version 2.3.2 (25.10.2019) # Core - [fix] wrong WAL ring buffer size calculation on load from storage -- [fix] Make storage autorepair optional +- [fix] Make storage auto-repair optional - [fix] firstSortIndex assert on sort by hash indexes # Version 2.3.0 (11.10.2019) @@ -1189,7 +1222,7 @@ Storages for v3 and v4 are compatible in both ways. - [fix] Idset cache invalidation on upsert/delete null values to indexes - [fix] Possible crash if sort orders disabled -- [fix] Wrong lowercasing field name on SQL UPDATE query +- [fix] Wrong lowercase field name on SQL UPDATE query - [fea] Delete & Update queries in transactions ## Reindexer tool @@ -1208,7 +1241,7 @@ Storages for v3 and v4 are compatible in both ways. - [fix] Fulltext queries sort by another field - [fea] Number of background threads for sort optimization can be changed from #config namespace -- [fix] Sort optimization choose logic is improoved +- [fix] Sort optimization choose logic is improved ## go connector @@ -1220,14 +1253,14 @@ Storages for v3 and v4 are compatible in both ways. ## Core -- [fea] More effective usage of btree index for GT/LT and sort in concurent read write operations +- [fea] More effective usage of btree index for GT/LT and sort in concurrent read write operations - [fix] Potential crash on index update or deletion - [fea] Timeout of background indexes optimization can be changed from #config namespace ## Reindexer server - [fea] User list moved from users.json to users.yml -- [fea] Hash is used insead of plain password in users.yml file +- [fea] Hash is used instead of plain password in users.yml file - [fix] Pass operation timeout from cproto client to core # Version 2.2.1 (07.09.2019) @@ -1236,7 +1269,7 @@ Storages for v3 and v4 are compatible in both ways. - [fea] Updated behaviour of Or InnerJoin statement - [fea] Store backups of system records in storage -- [fix] Replicator can start before db initalization completed +- [fix] Replicator can start before db initialization completed - [fix] Search prefixes if enabled only postfixes ## Reindexer server @@ -1256,7 +1289,7 @@ Storages for v3 and v4 are compatible in both ways. - [fea] Facets by array fields - [fea] JOIN now can be used in expression with another query conditions - [fea] Support rocksdb as storage engine -- [fix] Race on concurent read from system namespaces +- [fix] Race on concurrent read from system namespaces - [fix] Replication config sync fixed ## Reindexer tool @@ -1278,33 +1311,33 @@ Storages for v3 and v4 are compatible in both ways. ## Core -- [fea] Added two way sync of replication config and namespace +- [fea] Added two-way sync of replication config and namespace - [fea] Memory usage of indexes decreased (tsl::sparesmap has been added) - [fea] Added non-normalized query in queries stats - [fea] Add truncate namespace function -- [fix] Fixed unexpected hang and huge memory alloc on select by uncommited indexes +- [fix] Fixed unexpected hang and huge memory alloc on select by uncommitted indexes - [fix] Correct usage of '*' entry as default in namespaces config -- [fix] Memory statistics calculation are improoved +- [fix] Memory statistics calculation are improved - [fix] Slave will not try to clear expired by ttl records # Version 2.1.2 (04.08.2019) ## Core -- [fea] Added requests execution timeouts and cancelation contexts +- [fea] Added requests execution timeouts and cancellation contexts - [fea] Join memory consumption optimization - [fea] Current database activity statistics - [fea] Use composite indexes for IN condition to index's fields -- [fea] Reset perfomance and queries statistics by write to corresponding namespace +- [fea] Reset performance and queries statistics by write to corresponding namespace - [fix] Crashes on index removal - [fix] Do not lock namespace on tx operations -- [fix] SQL dumper will not add exceeded bracets +- [fix] SQL dumper will not add exceeded brackets - [fea] Added `updated_at` field to namespace attributes # go connector -- [fea] Added requests execution timeouts and cancelation contexts +- [fea] Added requests execution timeouts and cancellation contexts - [fea] Added async tx support - [fea] Removed (moved to core) `updated_at` legacy code @@ -1331,10 +1364,10 @@ Storages for v3 and v4 are compatible in both ways. ## Core -- [fea] Bracets in DSL & SQL queries +- [fea] Brackets in DSL & SQL queries - [fix] Crash on LRUCache fast invalidation - [fix] Relaxed JSON validation. Symbols with codes < 0x20 now are valid -- [fix] '\0' symbol in JSON will not broke parser +- [fix] The '\0' character in JSON will not break the parser - [fea] Backtrace with line numbers for debug builds - [fix] Replication fixes - [fea] Support for jemalloc pprof features @@ -1358,22 +1391,22 @@ Storages for v3 and v4 are compatible in both ways. # Version 2.0.3 (04.04.2019) ## Core -- [fea] Facets API improoved. Multiply fields and SORT features +- [fea] Facets API improved. Multiply fields and SORT features - [fea] TTL added - [fea] `LIKE` condition added - [fea] Add expressions support in SQL `UPDATE` statement - [fix] Invalid JSON generation with empty object name -- [fix] Unneccessary updating of tagsmatcher on transactions +- [fix] Unnecessary updating of tagsmatcher on transactions - [fix] LRUCache invalidation crash fix # Reindexer server -- [fea] Added metadata maniplulation methods +- [fea] Added metadata manipulation methods ## Face -- [fea] Added metadata maniplulation GUI -- [fix] Performance statistics GUI improovements +- [fea] Added metadata manipulation GUI +- [fix] Performance statistics GUI improvements # Version 2.0.2 (08.03.2019) @@ -1381,13 +1414,13 @@ Storages for v3 and v4 are compatible in both ways. - [fea] Update fields of documents, with SQL `UPDATE` statement support - [fea] Add SQL query suggestions - [fea] Add `DISTINCT` support to SQL query -- [fea] Queries to non nullable indexes with NULL condition will return error +- [fea] Queries to non-nullable indexes with NULL condition will return error - [fix] Fixes of full text search, raised on incremental index build - [fix] Queries with forced sort order can return wrong sequences - [fix] RPC client&replicator multithread races - [fix] DISTINCT condition to store indexes - [fix] Caches crash on too fast data invalidation -- [fix] Disable execiton of delete query from namespace in slave mode +- [fix] Disable execution of delete query from namespace in slave mode - [fix] Rebuild fulltext index if configuration changed - [fix] Fixed handling SQL numeric conditions values with extra leading 0 @@ -1434,7 +1467,7 @@ Storages for v3 and v4 are compatible in both ways. ## Go connector -- [fix] Struct verificator incorrect validation of composite `reindex` tags +- [fix] Struct verifier incorrect validation of composite `reindex` tags - [fea] Pool usage statistics added to `DB.Status()` method ## Reindexer server @@ -1445,7 +1478,7 @@ Storages for v3 and v4 are compatible in both ways. - [fea] Query builder added - [fea] `Delete all` button added to items page -- [fea] Aggregations results view +- [fea] Aggregation results view - [fea] Edit/Delete function of query results added - [fea] JSON index configuration editor - [fea] Memory usage statistics round precision @@ -1462,7 +1495,7 @@ Storages for v3 and v4 are compatible in both ways. - [fix] Invalid http redirects, if compiled with -DLINK_RESOURCES ## Reindexer tool -- [fix] Unhandled exception in case trying of create output file in unexisting directory +- [fix] Unhandled exception in case trying of create output file in non-existing directory - [fix] RPC client optimizations and races fixes - [fea] \bench command added @@ -1471,7 +1504,7 @@ Storages for v3 and v4 are compatible in both ways. ## Core -- [fea] Indexes rebuilding now is non blocking background task, concurrent R-W queries performance increased +- [fea] Indexes rebuilding now is non-blocking background task, concurrent R-W queries performance increased - [fix] Fulltext index incremental rebuild memory grow fixed ## Reindexer server @@ -1496,12 +1529,12 @@ Storages for v3 and v4 are compatible in both ways. ## Reindexer server -- [fea] REST API documentation improoved +- [fea] REST API documentation improved - [fea] Optimized performance ## Reindexer tool -- [fea] Operation speed is improoved +- [fea] Operation speed is improved # Version 1.10.0 (29.10.2018) @@ -1522,7 +1555,7 @@ Storages for v3 and v4 are compatible in both ways. ## Go connector - [fea] reindexer.Status method added, to check connector status after initialization -- [fea] OpenNamespace now register namespace <-> struct mapping without server connection requiriment +- [fea] OpenNamespace now register namespace <-> struct mapping without server connection requirement - [fix] int type is now converted to int32/int64 depends on architecture ## Python connector @@ -1531,7 +1564,7 @@ Storages for v3 and v4 are compatible in both ways. ## Reindexer server -- [fea] Added fields filter to method GET /api/v1/:db/:namespace:/items mathed +- [fea] Added fields filter to method GET /api/v1/:db/:namespace:/items matched - [fea] Added method DELETE /api/v1/:db/query - [fea] Added poll loop backend (osx,bsd) - [ref] `json_path` renamed to `json_paths`, and now array @@ -1548,7 +1581,7 @@ Storages for v3 and v4 are compatible in both ways. ## Core - [fea] Storing index configuration in storage -- [fea] Concurent R-W queries performance optimization +- [fea] Concurrent R-W queries performance optimization - [fea] Added indexes runtime performance statistics - [fix] Incorrect NOT behaviour on queries with only comparators - [fix] Race condition on shutdown @@ -1561,31 +1594,31 @@ Storages for v3 and v4 are compatible in both ways. - [fix] Multiple database support in `embeded` mode. ## Reindexer tool -- [fix] Fixed restoring namespaces with index names non equal to json paths +- [fix] Fixed restoring namespaces with index names non-equal to json paths # Version 1.9.6 (03.09.2018) ## Core - [fea] Merge with Join queries support - [fea] Sort by multiple columns/indexes -- [fix] Case insensivity for index/namespaces names +- [fix] Case insensitivity for index/namespaces names - [fix] Sparse indexes behavior fixed -- [fix] Full text index - correct calculatuon of distance between words -- [fix] Race condition on concurent ConfigureIndex requests +- [fix] Full text index - correct calculation of distance between words +- [fix] Race condition on concurrent ConfigureIndex requests ## Reindexer server - [fea] Added modify index method ## Go connector -- [fea] New builtinserver binding: builtin mode for go application + bundled server for external clients -- [fea] Improoved validation of go struct `reindex` tags +- [fea] New built-in server binding: builtin mode for go application + bundled server for external clients +- [fea] Improved validation of go struct `reindex` tags # Version 1.9.5 (04.08.2018) ## Core - [fea] Sparse indexes -- [fix] Fixed errors on conditions to unindexed fields +- [fix] Fixed errors in conditions for non-indexed fields - [fix] Fulltext terms relevancy, then query contains 2 terms included to single word - [fea] Customizable symbols set of "words" symbols for fulltext - [fix] Incorrect behavior on addition index with duplicated json path of another index @@ -1603,7 +1636,7 @@ Storages for v3 and v4 are compatible in both ways. ## Face -- [fix] Incorrect urlencode for document update API url +- [fix] Incorrect urlencoded for document update API url - [fix] Namespace view layout updated, jsonPath added to table ## Go connector @@ -1617,12 +1650,12 @@ Storages for v3 and v4 are compatible in both ways. - [fea] Conditions to any fields, even not indexed - [fea] cproto network client added -- [fix] Query execution plan optimizator fixes. +- [fix] Query execution plan optimizator fixes. ## Reindexer tool - [fea] Command line editor. tool has been mostly rewritten at all -- [fea] Interopertion with standalone server +- [fea] Interoperation with standalone server ## Reindexer server @@ -1639,7 +1672,7 @@ Storages for v3 and v4 are compatible in both ways. ## Core -- [fea] Added system namespaces #memstats #profstats #queriesstats #namespaces with executuin and profiling statistics +- [fea] Added system namespaces #memstats #profstats #queriesstats #namespaces with execution and profiling statistics - [fea] Added system namespace #config with runtime profiling configuration - [fix] Join cache memory limitation - [fix] Fixed bug with cjson parsing in nested objects on delete @@ -1651,7 +1684,7 @@ Storages for v3 and v4 are compatible in both ways. ## Reindexer Server - [fea] Load data in multiple threads on startup -- [fea] Auto rebalance connection between worker threads +- [fea] Auto re-balance connection between worker threads - [fix] "Authorization" http header case insensitivity lookup - [fix] Unexpected exit on SIGPIPE - [fix] Namespaces names are now url decoded @@ -1757,7 +1790,7 @@ Storages for v3 and v4 are compatible in both ways. ## C++ core -- [fea] Support join, marge, aggregations in json DSL & SQL queris +- [fea] Support join, marge, aggregations in json DSL & SQL queries - [fea] Added multiline form and comments in SQL query - [fix] Last symbol of documents was not used by fulltext indexer - [fix] Potential data corruption after removing index @@ -1793,7 +1826,7 @@ Storages for v3 and v4 are compatible in both ways. ## Reindexer server beta released: - [fea] Added cmake package target for RPM & DEB based systems -- [fea] sysv5 initscript added +- [fea] sysv5 init script added - [fea] Binary cproto RPC protocol introduced - [fea] Graceful server shutdown on SIGTERM and SIGINT - [fea] Multiply databases support was implemented diff --git a/clang-tidy/.clang-tidy b/clang-tidy/.clang-tidy index 23ccde514..063df74f1 100644 --- a/clang-tidy/.clang-tidy +++ b/clang-tidy/.clang-tidy @@ -13,16 +13,17 @@ Checks: 'clang-diagnostic-*, -bugprone-assignment-in-if-condition, -bugprone-parent-virtual-call, -bugprone-integer-division, - -bugprone-unhandled-self-assignment + -bugprone-unhandled-self-assignment, + -bugprone-inc-dec-in-conditions, -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, -performance-no-int-to-ptr, + -performance-enum-size, -performance-avoid-endl' # clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling - too many unnecessary warning in vendored code # performance-no-int-to-ptr - consider how to fix this # bugprone-macro-parentheses - consider fixing WarningsAsErrors: '*' HeaderFilterRegex: '.*(? *, 2> dss; int shardId = (const_cast(this))->GetShardID(); - // std::cout << "getJSONFromCJSON shardId=" << shardId << std::endl; AdditionalDatasourceShardId dsShardId(shardId); if (qr_->NeedOutputShardId() && shardId >= 0) { dss.push_back(&dsShardId); diff --git a/cpp_src/client/cororeindexer.cc b/cpp_src/client/cororeindexer.cc index 090bca865..6d815e6e3 100644 --- a/cpp_src/client/cororeindexer.cc +++ b/cpp_src/client/cororeindexer.cc @@ -46,7 +46,7 @@ Error CoroReindexer::CreateTemporaryNamespace(std::string_view baseName, std::st return impl_->CreateTemporaryNamespace(baseName, resultName, ctx_, opts, version); } Error CoroReindexer::TruncateNamespace(std::string_view nsName) { return impl_->TruncateNamespace(nsName, ctx_); } -Error CoroReindexer::RenameNamespace(std::string_view srcNsName, const std::string& dstNsName) { +Error CoroReindexer::RenameNamespace(std::string_view srcNsName, std::string_view dstNsName) { return impl_->RenameNamespace(srcNsName, dstNsName, ctx_); } Error CoroReindexer::Insert(std::string_view nsName, Item& item) { return impl_->Insert(nsName, item, RPCDataFormat::CJSON, ctx_); } @@ -67,17 +67,16 @@ Error CoroReindexer::GetMeta(std::string_view nsName, const std::string& key, st Error CoroReindexer::PutMeta(std::string_view nsName, const std::string& key, std::string_view data) { return impl_->PutMeta(nsName, key, data, ctx_); } -Error CoroReindexer::EnumMeta(std::string_view nsName, std::vector& keys) { - return impl_->EnumMeta(nsName, keys, ctx_); -} -Error CoroReindexer::DeleteMeta(std::string_view nsName, const std::string& key) { - return impl_->DeleteMeta(nsName, key, ctx_); -} +Error CoroReindexer::EnumMeta(std::string_view nsName, std::vector& keys) { return impl_->EnumMeta(nsName, keys, ctx_); } +Error CoroReindexer::DeleteMeta(std::string_view nsName, const std::string& key) { return impl_->DeleteMeta(nsName, key, ctx_); } Error CoroReindexer::Delete(const Query& q, CoroQueryResults& result) { return impl_->Delete(q, result, ctx_); } Error CoroReindexer::Delete(std::string_view nsName, std::string_view cjson) { return impl_->Delete(nsName, cjson, ctx_); } Error CoroReindexer::Select(std::string_view query, CoroQueryResults& result) { return impl_->Select(query, result, ctx_); } Error CoroReindexer::Select(const Query& q, CoroQueryResults& result) { return impl_->Select(q, result, ctx_); } -Error CoroReindexer::Commit(std::string_view nsName) { return impl_->Commit(nsName, ctx_); } +Error CoroReindexer::Commit(std::string_view) { + // Empty + return {}; +} Error CoroReindexer::AddIndex(std::string_view nsName, const IndexDef& idx) { return impl_->AddIndex(nsName, idx, ctx_); } Error CoroReindexer::UpdateIndex(std::string_view nsName, const IndexDef& idx) { return impl_->UpdateIndex(nsName, idx, ctx_); } Error CoroReindexer::DropIndex(std::string_view nsName, const IndexDef& index) { return impl_->DropIndex(nsName, index, ctx_); } diff --git a/cpp_src/client/cororeindexer.h b/cpp_src/client/cororeindexer.h index 2dca93085..a1d1f3835 100644 --- a/cpp_src/client/cororeindexer.h +++ b/cpp_src/client/cororeindexer.h @@ -85,7 +85,7 @@ class CoroReindexer { /// Rename namespace. If namespace with dstNsName exists, then it is replaced. /// @param srcNsName - Name of namespace /// @param dstNsName - desired name of namespace - Error RenameNamespace(std::string_view srcNsName, const std::string &dstNsName); + Error RenameNamespace(std::string_view srcNsName, std::string_view dstNsName); /// Add index to namespace /// @param nsName - Name of namespace /// @param index - IndexDef with index name and parameters @@ -163,8 +163,8 @@ class CoroReindexer { /// @param query - Query object with query attributes /// @param result - QueryResults with found items Error Select(const Query &query, CoroQueryResults &result); - /// Flush changes to storage - /// @param nsName - Name of namespace + /// *DEPRECATED* This method does nothing + /// TODO: Must be removed after python-binding update #1800 Error Commit(std::string_view nsName); /// Allocate new item for namespace /// @param nsName - Name of namespace diff --git a/cpp_src/client/item.h b/cpp_src/client/item.h index a2c11be33..e8a756e82 100644 --- a/cpp_src/client/item.h +++ b/cpp_src/client/item.h @@ -50,7 +50,7 @@ class Item { /// If Item is in *Unsafe Mode*, then Item will not store slice, but just keep pointer to data in slice, /// application *MUST* hold slice until end of life of Item /// @param slice - data slice with CJson - [[nodiscard]] Error FromCJSON(std::string_view slice) &noexcept; + Error FromCJSON(std::string_view slice) &noexcept; void FromCJSONImpl(std::string_view slice) &; /// Serialize item to CJSON.
/// If Item is in *Unfafe Mode*, then returned slice is allocated in temporary buffer, and can be invalidated by any next operation with diff --git a/cpp_src/client/namespace.h b/cpp_src/client/namespace.h index 398834e67..93044654c 100644 --- a/cpp_src/client/namespace.h +++ b/cpp_src/client/namespace.h @@ -1,6 +1,5 @@ #pragma once -#include #include "client/item.h" #include "core/cjson/tagsmatcher.h" #include "core/payload/payloadtype.h" diff --git a/cpp_src/client/queryresults.cc b/cpp_src/client/queryresults.cc index 09e10d142..3b52515b5 100644 --- a/cpp_src/client/queryresults.cc +++ b/cpp_src/client/queryresults.cc @@ -55,7 +55,8 @@ Error QueryResults::setClient(ReindexerImpl* rx) { QueryResults::~QueryResults() { if (rx_ && results_.holdsRemoteData()) { - rx_->closeResults(*this); + auto err = rx_->closeResults(*this); + (void)err; // ignore } results_.setClosed(); } diff --git a/cpp_src/client/reindexer.cc b/cpp_src/client/reindexer.cc index 1c8d2d4f7..4834b0fbb 100644 --- a/cpp_src/client/reindexer.cc +++ b/cpp_src/client/reindexer.cc @@ -44,16 +44,11 @@ Error Reindexer::GetMeta(std::string_view nsName, const std::string& key, std::v Error Reindexer::PutMeta(std::string_view nsName, const std::string& key, std::string_view data) { return impl_->PutMeta(nsName, key, data, ctx_); } -Error Reindexer::EnumMeta(std::string_view nsName, std::vector& keys) { - return impl_->EnumMeta(nsName, keys, ctx_); -} -Error Reindexer::DeleteMeta(std::string_view nsName, const std::string& key) { - return impl_->DeleteMeta(nsName, key, ctx_); -} +Error Reindexer::EnumMeta(std::string_view nsName, std::vector& keys) { return impl_->EnumMeta(nsName, keys, ctx_); } +Error Reindexer::DeleteMeta(std::string_view nsName, const std::string& key) { return impl_->DeleteMeta(nsName, key, ctx_); } Error Reindexer::Delete(const Query& q, QueryResults& result) { return impl_->Delete(q, result, ctx_); } Error Reindexer::Select(std::string_view query, QueryResults& result) { return impl_->Select(query, result, ctx_); } Error Reindexer::Select(const Query& q, QueryResults& result) { return impl_->Select(q, result, ctx_); } -Error Reindexer::Commit(std::string_view nsName) { return impl_->Commit(nsName, ctx_); } Error Reindexer::AddIndex(std::string_view nsName, const IndexDef& idx) { return impl_->AddIndex(nsName, idx, ctx_); } Error Reindexer::UpdateIndex(std::string_view nsName, const IndexDef& idx) { return impl_->UpdateIndex(nsName, idx, ctx_); } Error Reindexer::DropIndex(std::string_view nsName, const IndexDef& index) { return impl_->DropIndex(nsName, index, ctx_); } diff --git a/cpp_src/client/reindexer.h b/cpp_src/client/reindexer.h index 0f3079dcb..cb20e5584 100644 --- a/cpp_src/client/reindexer.h +++ b/cpp_src/client/reindexer.h @@ -151,9 +151,6 @@ class Reindexer { /// @param result - QueryResults with found items Error Select(const Query &query, QueryResults &result); - /// Flush changes to storage - /// @param nsName - Name of namespace - Error Commit(std::string_view nsName); /// Allocate new item for namespace /// @param nsName - Name of namespace /// @return Item ready for filling and further Upsert/Insert/Delete/Update call diff --git a/cpp_src/client/reindexerimpl.cc b/cpp_src/client/reindexerimpl.cc index 6cc4adbf0..c1dfad7f5 100644 --- a/cpp_src/client/reindexerimpl.cc +++ b/cpp_src/client/reindexerimpl.cc @@ -58,11 +58,10 @@ Error ReindexerImpl::Connect(const std::string &dsn, const client::ConnectOpts & return ret; } -Error ReindexerImpl::Stop() { +void ReindexerImpl::Stop() { std::lock_guard lock(workersMtx_); requiresStatusCheck_.store(true, std::memory_order_relaxed); stop(); - return Error(); } Error ReindexerImpl::OpenNamespace(std::string_view nsName, const InternalRdxContext &ctx, const StorageOpts &opts, const NsReplicationOpts &replOpts) { @@ -152,9 +151,6 @@ Error ReindexerImpl::Select(const Query &query, QueryResults &result, const Inte if (!err.ok()) return err; return sendCommand(DbCmdSelectQ, ctx, query, result.results_); } -Error ReindexerImpl::Commit(std::string_view nsName, const InternalRdxContext &ctx) { - return sendCommand(DbCmdCommit, ctx, std::move(nsName)); -} Item ReindexerImpl::NewItem(std::string_view nsName, const InternalRdxContext &ctx) { return sendCommand(DbCmdNewItem, ctx, std::move(nsName)); @@ -325,7 +321,8 @@ void ReindexerImpl::threadLoopFun(uint32_t tid, std::promise &isRunning, const auto obsID = conn.rx.AddConnectionStateObserver( [this](const Error &e) noexcept { requiresStatusCheck_.store(!e.ok(), std::memory_order_relaxed); }); wg.wait(); - conn.rx.RemoveConnectionStateObserver(obsID); + err = conn.rx.RemoveConnectionStateObserver(obsID); + (void)err; // ignore th.commandAsync.stop(); th.closeAsync.stop(); @@ -528,10 +525,6 @@ void ReindexerImpl::coroInterpreter(Connection &conn, Connectio cmd, [&conn, &cmd](const Query &query, CoroQueryResults &result) { return conn.rx.Select(query, result, cmd->ctx); }); break; } - case DbCmdCommit: { - execCommand(cmd, [&conn, &cmd](std::string_view nsName) { return conn.rx.Commit(nsName, cmd->ctx); }); - break; - } case DbCmdGetMeta: { execCommand(cmd, [&conn, &cmd](std::string_view nsName, const std::string &key, std::string &data) { return conn.rx.GetMeta(nsName, key, data, cmd->ctx); diff --git a/cpp_src/client/reindexerimpl.h b/cpp_src/client/reindexerimpl.h index a8306998f..79f22c9e9 100644 --- a/cpp_src/client/reindexerimpl.h +++ b/cpp_src/client/reindexerimpl.h @@ -36,7 +36,7 @@ class ReindexerImpl { ReindexerImpl &operator=(const ReindexerImpl &) = delete; Error Connect(const std::string &dsn, const client::ConnectOpts &opts = client::ConnectOpts()); - Error Stop(); + void Stop(); Error OpenNamespace(std::string_view nsName, const InternalRdxContext &ctx, const StorageOpts &opts, const NsReplicationOpts &replOpts); Error AddNamespace(const NamespaceDef &nsDef, const InternalRdxContext &ctx, const NsReplicationOpts &replOpts); Error CloseNamespace(std::string_view nsName, const InternalRdxContext &ctx); @@ -62,7 +62,6 @@ class ReindexerImpl { Error Delete(const Query &query, QueryResults &result, const InternalRdxContext &ctx); Error Select(std::string_view query, QueryResults &result, const InternalRdxContext &ctx); Error Select(const Query &query, QueryResults &result, const InternalRdxContext &ctx); - Error Commit(std::string_view nsName, const InternalRdxContext &ctx); Item NewItem(std::string_view nsName, const InternalRdxContext &ctx); Error GetMeta(std::string_view nsName, const std::string &key, std::string &data, const InternalRdxContext &ctx); Error GetMeta(std::string_view nsName, const std::string &key, std::vector &data, const InternalRdxContext &ctx); @@ -124,7 +123,6 @@ class ReindexerImpl { DbCmdNewItem, DbCmdSelectS, DbCmdSelectQ, - DbCmdCommit, DbCmdGetMeta, DbCmdGetShardedMeta, DbCmdPutMeta, diff --git a/cpp_src/client/resultserializer.cc b/cpp_src/client/resultserializer.cc index d38780d21..883b6a8b3 100644 --- a/cpp_src/client/resultserializer.cc +++ b/cpp_src/client/resultserializer.cc @@ -65,11 +65,15 @@ void ResultSerializer::GetExtraParams(ResultSerializer::QueryParams& ret, Option ret.explainResults.emplace(); } // firstLazyData guaranties, that aggResults will be non-'nullopt' - ret.aggResults->emplace_back(); // NOLINT(bugprone-unchecked-optional-access) + auto& aggRes = ret.aggResults->emplace_back(); // NOLINT(bugprone-unchecked-optional-access) + Error err; if ((ret.flags & kResultsFormatMask) == kResultsMsgPack) { - ret.aggResults->back().FromMsgPack(data); // NOLINT(bugprone-unchecked-optional-access) + err = aggRes.FromMsgPack(data); } else { - ret.aggResults->back().FromJSON(giftStr(data)); // NOLINT(bugprone-unchecked-optional-access) + err = aggRes.FromJSON(giftStr(data)); + } + if (!err.ok()) { + throw err; } } break; diff --git a/cpp_src/client/rpcclient.cc b/cpp_src/client/rpcclient.cc index 14bf0e828..435a27c04 100644 --- a/cpp_src/client/rpcclient.cc +++ b/cpp_src/client/rpcclient.cc @@ -115,7 +115,7 @@ Error RPCClient::TruncateNamespace(std::string_view nsName, const InternalRdxCon return conn_.Call(mkCommand(cproto::kCmdTruncateNamespace, &ctx), nsName).Status(); } -Error RPCClient::RenameNamespace(std::string_view srcNsName, const std::string& dstNsName, const InternalRdxContext& ctx) { +Error RPCClient::RenameNamespace(std::string_view srcNsName, std::string_view dstNsName, const InternalRdxContext& ctx) { auto status = conn_.Call(mkCommand(cproto::kCmdRenameNamespace, &ctx), srcNsName, dstNsName).Status(); if (!status.ok() && status.code() != errTimeout) return status; if (srcNsName != dstNsName) { @@ -474,10 +474,6 @@ Error RPCClient::selectImpl(const Query& query, CoroQueryResults& result, millis return ret.Status(); } -Error RPCClient::Commit(std::string_view nsName, const InternalRdxContext& ctx) { - return conn_.Call(mkCommand(cproto::kCmdCommit, &ctx), nsName).Status(); -} - Error RPCClient::AddIndex(std::string_view nsName, const IndexDef& iDef, const InternalRdxContext& ctx) { WrSerializer ser; iDef.GetJSON(ser); @@ -719,7 +715,7 @@ Error RPCClient::SuggestLeader(const NodeData& suggestion, NodeData& response, c gason::JsonParser parser; auto json = ret.GetArgs(1)[0].As(); auto root = parser.Parse(giftStr(json)); - response.FromJSON(root); + return response.FromJSON(root); } catch (Error& err) { return err; } @@ -760,7 +756,7 @@ Error RPCClient::GetRaftInfo(RaftInfo& info, const InternalRdxContext& ctx) { gason::JsonParser parser; auto json = ret.GetArgs(1)[0].As(); auto root = parser.Parse(giftStr(json)); - info.FromJSON(root); + return info.FromJSON(root); } catch (Error& err) { return err; } diff --git a/cpp_src/client/rpcclient.h b/cpp_src/client/rpcclient.h index ca73d9d68..1d31ee59e 100644 --- a/cpp_src/client/rpcclient.h +++ b/cpp_src/client/rpcclient.h @@ -92,7 +92,7 @@ class RPCClient { Error CreateTemporaryNamespace(std::string_view baseName, std::string &resultName, const InternalRdxContext &ctx, const StorageOpts &opts = StorageOpts().Enabled(), lsn_t version = lsn_t()); Error TruncateNamespace(std::string_view nsName, const InternalRdxContext &ctx); - Error RenameNamespace(std::string_view srcNsName, const std::string &dstNsName, const InternalRdxContext &ctx); + Error RenameNamespace(std::string_view srcNsName, std::string_view dstNsName, const InternalRdxContext &ctx); Error AddIndex(std::string_view nsName, const IndexDef &index, const InternalRdxContext &ctx); Error UpdateIndex(std::string_view nsName, const IndexDef &index, const InternalRdxContext &ctx); Error DropIndex(std::string_view nsName, const IndexDef &index, const InternalRdxContext &ctx); @@ -126,7 +126,6 @@ class RPCClient { Error Select(const Query &query, CoroQueryResults &result, const InternalRdxContext &ctx) { return selectImpl(query, result, config_.NetTimeout, ctx); } - Error Commit(std::string_view nsName, const InternalRdxContext &ctx); Item NewItem(std::string_view nsName); template Item NewItem(std::string_view nsName, C &client, std::chrono::milliseconds execTimeout) { diff --git a/cpp_src/client/transaction.cc b/cpp_src/client/transaction.cc index 708631c86..b9db6af43 100644 --- a/cpp_src/client/transaction.cc +++ b/cpp_src/client/transaction.cc @@ -19,7 +19,8 @@ Item Transaction::NewItem() { Transaction::~Transaction() { if (!IsFree()) { - rx_->RollBackTransaction(*this, InternalRdxContext()); + auto err = rx_->RollBackTransaction(*this, InternalRdxContext()); + (void)err; // ignore } tr_.clear(); } diff --git a/cpp_src/cluster/clusterizator.cc b/cpp_src/cluster/clusterizator.cc index 0c17c88bf..3bcb4b3e7 100644 --- a/cpp_src/cluster/clusterizator.cc +++ b/cpp_src/cluster/clusterizator.cc @@ -1,6 +1,4 @@ #include "clusterizator.h" -#include -#include #include "core/reindexer_impl/reindexerimpl.h" namespace reindexer { @@ -113,14 +111,10 @@ bool Clusterizator::NamesapceIsInReplicationConfig(std::string_view nsName) { return clusterReplicator_.NamespaceIsInClusterConfig(nsName) || asyncReplicator_.NamespaceIsInAsyncConfig(nsName); } -Error Clusterizator::Replicate(UpdateRecord&& rec, std::function beforeWaitF, const RdxContext& ctx) { - UpdatesContainer recs(1); - recs[0] = std::move(rec); - return Clusterizator::Replicate(std::move(recs), std::move(beforeWaitF), ctx); -} - Error Clusterizator::Replicate(UpdatesContainer&& recs, std::function beforeWaitF, const RdxContext& ctx) { - if (replicationIsNotRequired(recs)) return errOK; + if (replicationIsNotRequired(recs)) { + return {}; + } std::pair res; if (ctx.GetOriginLSN().isEmpty()) { @@ -132,17 +126,13 @@ Error Clusterizator::Replicate(UpdatesContainer&& recs, std::function be if (res.second) { return res.first; } - return Error(); // This namespace is not taking part in any replication -} - -Error Clusterizator::ReplicateAsync(UpdateRecord&& rec, const RdxContext& ctx) { - UpdatesContainer recs(1); - recs[0] = std::move(rec); - return Clusterizator::ReplicateAsync(std::move(recs), ctx); + return {}; // This namespace is not taking part in any replication } Error Clusterizator::ReplicateAsync(UpdatesContainer&& recs, const RdxContext& ctx) { - if (replicationIsNotRequired(recs)) return errOK; + if (replicationIsNotRequired(recs)) { + return {}; + } std::pair res; if (ctx.GetOriginLSN().isEmpty()) { @@ -151,13 +141,11 @@ Error Clusterizator::ReplicateAsync(UpdatesContainer&& recs, const RdxContext& c // Update can't be replicated to cluster from another node, so may only be replicated to async replicas res = updatesQueue_.PushAsync(std::move(recs)); } - return Error(); // This namespace is not taking part in any replication + return {}; // This namespace is not taking part in any replication } bool Clusterizator::replicationIsNotRequired(const UpdatesContainer& recs) noexcept { - if (recs.empty()) return true; - std::string_view name = recs[0].GetNsName(); - return name.size() && name[0] == '#'; + return recs.empty() || isSystemNamespaceNameFast(recs[0].NsName()); } void Clusterizator::validateConfig() const { diff --git a/cpp_src/cluster/clusterizator.h b/cpp_src/cluster/clusterizator.h index eb835b1ec..6ae19286b 100644 --- a/cpp_src/cluster/clusterizator.h +++ b/cpp_src/cluster/clusterizator.h @@ -1,9 +1,8 @@ #pragma once -#include "client/cororeindexer.h" #include "cluster/config.h" #include "core/dbconfig.h" -#include "insdatareplicator.h" +#include "idatareplicator.h" #include "net/ev/ev.h" #include "replication/asyncdatareplicator.h" #include "replication/clusterdatareplicator.h" @@ -14,7 +13,7 @@ class ReindexerImpl; namespace cluster { -class Clusterizator : public INsDataReplicator { +class Clusterizator : public IDataReplicator, public IDataSyncer { public: Clusterizator(ReindexerImpl &thisNode, size_t maxUpdatesSize); @@ -37,11 +36,9 @@ class Clusterizator : public INsDataReplicator { bool NamespaceIsInClusterConfig(std::string_view nsName); bool NamesapceIsInReplicationConfig(std::string_view nsName); - Error Replicate(UpdateRecord &&rec, std::function beforeWaitF, const RdxContext &ctx) override final; Error Replicate(UpdatesContainer &&recs, std::function beforeWaitF, const RdxContext &ctx) override final; - Error ReplicateAsync(UpdateRecord &&rec, const RdxContext &ctx) override final; Error ReplicateAsync(UpdatesContainer &&recs, const RdxContext &ctx) override final; - void AwaitInitialSync(std::string_view nsName, const RdxContext &ctx) const override final { + void AwaitInitialSync(const NamespaceName &nsName, const RdxContext &ctx) const override final { if (enabled_.load(std::memory_order_acquire)) { sharedSyncState_.AwaitInitialSync(nsName, ctx); } @@ -51,7 +48,7 @@ class Clusterizator : public INsDataReplicator { sharedSyncState_.AwaitInitialSync(ctx); } } - bool IsInitialSyncDone(std::string_view name) const override final { + bool IsInitialSyncDone(const NamespaceName &name) const override final { return !enabled_.load(std::memory_order_acquire) || sharedSyncState_.IsInitialSyncDone(name); } bool IsInitialSyncDone() const override final { @@ -67,7 +64,7 @@ class Clusterizator : public INsDataReplicator { void validateConfig() const; mutable std::mutex mtx_; - UpdatesQueuePair updatesQueue_; + UpdatesQueuePair updatesQueue_; SharedSyncState<> sharedSyncState_; ClusterDataReplicator clusterReplicator_; AsyncDataReplicator asyncReplicator_; diff --git a/cpp_src/cluster/config.cc b/cpp_src/cluster/config.cc index 061de4347..ae5682a5d 100644 --- a/cpp_src/cluster/config.cc +++ b/cpp_src/cluster/config.cc @@ -28,13 +28,13 @@ static void ValidateDSN(std::string_view dsn) { Error NodeData::FromJSON(span json) { try { - FromJSON(gason::JsonParser().Parse(json)); + return FromJSON(gason::JsonParser().Parse(json)); } catch (const gason::Exception &ex) { return Error(errParseJson, "NodeData: %s", ex.what()); } catch (const Error &err) { return err; } - return errOK; + return {}; } Error NodeData::FromJSON(const gason::JsonNode &root) { @@ -47,7 +47,7 @@ Error NodeData::FromJSON(const gason::JsonNode &root) { } catch (const gason::Exception &ex) { return Error(errParseJson, "NodeData: %s", ex.what()); } - return errOK; + return {}; } void NodeData::GetJSON(JsonBuilder &jb) const { @@ -63,13 +63,13 @@ void NodeData::GetJSON(WrSerializer &ser) const { Error RaftInfo::FromJSON(span json) { try { - FromJSON(gason::JsonParser().Parse(json)); + return FromJSON(gason::JsonParser().Parse(json)); } catch (const gason::Exception &ex) { return Error(errParseJson, "RaftInfo: %s", ex.what()); } catch (const Error &err) { return err; } - return errOK; + return {}; } Error RaftInfo::FromJSON(const gason::JsonNode &root) { @@ -81,7 +81,7 @@ Error RaftInfo::FromJSON(const gason::JsonNode &root) { } catch (const gason::Exception &ex) { return Error(errParseJson, "RaftInfo: %s", ex.what()); } - return errOK; + return {}; } void RaftInfo::GetJSON(JsonBuilder &jb) const { @@ -135,7 +135,7 @@ void AsyncReplNodeConfig::FromYAML(const YAML::Node &root) { auto node = root["namespaces"]; namespaces_.reset(); if (node.IsSequence()) { - fast_hash_set nss; + NsNamesHashSetT nss; for (const auto &ns : node) { nss.emplace(ns.as()); } @@ -154,7 +154,7 @@ void AsyncReplNodeConfig::FromJSON(const gason::JsonNode &root) { { auto &node = root["namespaces"]; if (!node.empty()) { - fast_hash_set nss; + NsNamesHashSetT nss; for (auto &objNode : node) { nss.emplace(objNode.As()); } @@ -185,7 +185,7 @@ void AsyncReplNodeConfig::GetYAML(YAML::Node &yaml) const { yaml["namespaces"] = YAML::Node(YAML::NodeType::Sequence); if (namespaces_ && !namespaces_->Empty()) { for (auto &ns : namespaces_->data) { - yaml["namespaces"].push_back(ns); + yaml["namespaces"].push_back(std::string_view(ns)); } } } @@ -223,7 +223,7 @@ Error ClusterConfigData::FromYAML(const std::string &yaml) { auto node = root["namespaces"]; namespaces.clear(); for (const auto &ns : node) { - namespaces.insert(ns.as()); + namespaces.insert(NamespaceName(ns.as())); } } { @@ -263,7 +263,7 @@ Error AsyncReplConfigData::FromYAML(const std::string &yaml) { logLevel = logLevelFromString(root["log_level"].as("info")); { auto node = root["namespaces"]; - fast_hash_set nss; + NsNamesHashSetT nss; for (const auto &n : node) { nss.emplace(n.as()); } @@ -316,19 +316,20 @@ Error AsyncReplConfigData::FromJSON(const gason::JsonNode &root) { } } - tryReadOptionalJsonValue(&errorString, root, "app_name"sv, appName); - - tryReadOptionalJsonValue(&errorString, root, "sync_threads"sv, replThreadsCount); - tryReadOptionalJsonValue(&errorString, root, "syncs_per_thread"sv, parallelSyncsPerThreadCount); - tryReadOptionalJsonValue(&errorString, root, "online_updates_timeout_sec"sv, onlineUpdatesTimeoutSec); - tryReadOptionalJsonValue(&errorString, root, "sync_timeout_sec"sv, syncTimeoutSec); - tryReadOptionalJsonValue(&errorString, root, "force_sync_on_logic_error"sv, forceSyncOnLogicError); - tryReadOptionalJsonValue(&errorString, root, "force_sync_on_wrong_data_hash"sv, forceSyncOnWrongDataHash); - tryReadOptionalJsonValue(&errorString, root, "retry_sync_interval_msec"sv, retrySyncIntervalMSec); - tryReadOptionalJsonValue(&errorString, root, "enable_compression"sv, enableCompression); - tryReadOptionalJsonValue(&errorString, root, "batching_routines_count"sv, batchingRoutinesCount); - tryReadOptionalJsonValue(&errorString, root, "max_wal_depth_on_force_sync"sv, maxWALDepthOnForceSync); - tryReadOptionalJsonValue(&errorString, root, "online_updates_delay_msec"sv, onlineUpdatesDelayMSec); + auto err = tryReadOptionalJsonValue(&errorString, root, "app_name"sv, appName); + + err = tryReadOptionalJsonValue(&errorString, root, "sync_threads"sv, replThreadsCount); + err = tryReadOptionalJsonValue(&errorString, root, "syncs_per_thread"sv, parallelSyncsPerThreadCount); + err = tryReadOptionalJsonValue(&errorString, root, "online_updates_timeout_sec"sv, onlineUpdatesTimeoutSec); + err = tryReadOptionalJsonValue(&errorString, root, "sync_timeout_sec"sv, syncTimeoutSec); + err = tryReadOptionalJsonValue(&errorString, root, "force_sync_on_logic_error"sv, forceSyncOnLogicError); + err = tryReadOptionalJsonValue(&errorString, root, "force_sync_on_wrong_data_hash"sv, forceSyncOnWrongDataHash); + err = tryReadOptionalJsonValue(&errorString, root, "retry_sync_interval_msec"sv, retrySyncIntervalMSec); + err = tryReadOptionalJsonValue(&errorString, root, "enable_compression"sv, enableCompression); + err = tryReadOptionalJsonValue(&errorString, root, "batching_routines_count"sv, batchingRoutinesCount); + err = tryReadOptionalJsonValue(&errorString, root, "max_wal_depth_on_force_sync"sv, maxWALDepthOnForceSync); + err = tryReadOptionalJsonValue(&errorString, root, "online_updates_delay_msec"sv, onlineUpdatesDelayMSec); + (void)err; // Read errors do not matter here if (std::string_view levelStr = logLevelToString(logLevel); tryReadOptionalJsonValue(&errorString, root, "log_level"sv, levelStr).ok()) { @@ -337,7 +338,7 @@ Error AsyncReplConfigData::FromJSON(const gason::JsonNode &root) { } try { - fast_hash_set nss; + NsNamesHashSetT nss; for (auto &objNode : root["namespaces"]) { nss.emplace(objNode.As()); } @@ -366,8 +367,8 @@ Error AsyncReplConfigData::FromJSON(const gason::JsonNode &root) { if (!errorString.empty()) { return Error(errParseJson, "AsyncReplConfigData: JSON parsing error: '%s'", errorString); - } else - return Error(); + } + return Error(); } void AsyncReplConfigData::GetJSON(JsonBuilder &jb) const { @@ -400,12 +401,11 @@ void AsyncReplConfigData::GetJSON(JsonBuilder &jb) const { } void AsyncReplConfigData::GetYAML(WrSerializer &ser) const { - std::string namespacesStr, nodesStr; YAML::Node nss; nss["namespaces"] = YAML::Node(YAML::NodeType::Sequence); if (namespaces && !namespaces->Empty()) { for (auto &ns : namespaces->data) { - nss["namespaces"].push_back(ns); + nss["namespaces"].push_back(std::string_view(ns)); } } YAML::Node nds; @@ -672,7 +672,7 @@ sharding::Segment ShardingConfig::Key::SegmentFromJSON(const gason::Jso case gason::JsonTag::JSON_ARRAY: case gason::JsonTag::JSON_NULL: default: - throw Error(errParams, "Incorrect JsonTag for sharding key"); + throw Error(errParams, "Incorrect JsonTag for sharding key: %d", int(jsonValue.getTag())); } } diff --git a/cpp_src/cluster/config.h b/cpp_src/cluster/config.h index b7debbe96..49642100f 100644 --- a/cpp_src/cluster/config.h +++ b/cpp_src/cluster/config.h @@ -2,13 +2,11 @@ #include #include -#include #include "core/keyvalue/variant.h" -#include "estl/fast_hash_set.h" +#include "core/namespace/namespacenamesets.h" #include "estl/span.h" #include "sharding/ranges.h" #include "tools/errors.h" -#include "tools/stringstools.h" namespace gason { struct JsonNode; @@ -77,13 +75,13 @@ class AsyncReplNodeConfig { class NamespaceListImpl { public: NamespaceListImpl() {} - NamespaceListImpl(fast_hash_set&& n) : data(std::move(n)) {} - bool IsInList(std::string_view ns, size_t hash) const noexcept { return data.empty() || (data.find(ns, hash) != data.end()); } + NamespaceListImpl(NsNamesHashSetT&& n) : data(std::move(n)) {} + bool IsInList(const NamespaceName& ns) const noexcept { return data.empty() || (data.find(ns) != data.end()); } bool IsInList(std::string_view ns) const noexcept { return data.empty() || (data.find(ns) != data.end()); } bool Empty() const noexcept { return data.empty(); } bool operator==(const NamespaceListImpl& r) const noexcept { return data == r.data; } - const fast_hash_set data; + const NsNamesHashSetT data; }; using NamespaceList = intrusive_atomic_rc_wrapper; @@ -98,7 +96,7 @@ class AsyncReplNodeConfig { void GetYAML(YAML::Node& yaml) const; bool HasOwnNsList() const noexcept { return hasOwnNsList_; } - void SetOwnNamespaceList(fast_hash_set nss) { + void SetOwnNamespaceList(NsNamesHashSetT nss) { namespaces_ = make_intrusive(std::move(nss)); hasOwnNsList_ = true; } @@ -168,7 +166,7 @@ struct ClusterConfigData { } std::vector nodes; - fast_hash_set namespaces; + NsNamesHashSetT namespaces; std::string appName = "rx_cluster_node"; int onlineUpdatesTimeoutSec = 20; int syncTimeoutSec = 60; diff --git a/cpp_src/cluster/idatareplicator.h b/cpp_src/cluster/idatareplicator.h new file mode 100644 index 000000000..c00ea316f --- /dev/null +++ b/cpp_src/cluster/idatareplicator.h @@ -0,0 +1,28 @@ +#pragma once + +#include "core/rdxcontext.h" +#include "updates/updaterecord.h" + +namespace reindexer { +namespace cluster { + +using UpdatesContainer = h_vector; + +struct IDataReplicator { + virtual Error Replicate(UpdatesContainer &&recs, std::function beforeWaitF, const RdxContext &ctx) = 0; + virtual Error ReplicateAsync(UpdatesContainer &&recs, const RdxContext &ctx) = 0; + + virtual ~IDataReplicator() = default; +}; + +struct IDataSyncer { + virtual void AwaitInitialSync(const NamespaceName &nsName, const RdxContext &ctx) const = 0; + virtual void AwaitInitialSync(const RdxContext &ctx) const = 0; + virtual bool IsInitialSyncDone(const NamespaceName &nsName) const = 0; + virtual bool IsInitialSyncDone() const = 0; + + virtual ~IDataSyncer() = default; +}; + +} // namespace cluster +} // namespace reindexer diff --git a/cpp_src/cluster/insdatareplicator.h b/cpp_src/cluster/insdatareplicator.h deleted file mode 100644 index e8e56857e..000000000 --- a/cpp_src/cluster/insdatareplicator.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include "core/rdxcontext.h" -#include "updaterecord.h" - -namespace reindexer { -namespace cluster { - -using UpdatesContainer = h_vector; - -struct INsDataReplicator { - virtual Error Replicate(UpdateRecord &&rec, std::function beforeWaitF, const RdxContext &ctx) = 0; - virtual Error Replicate(UpdatesContainer &&recs, std::function beforeWaitF, const RdxContext &ctx) = 0; - virtual Error ReplicateAsync(UpdateRecord &&rec, const RdxContext &ctx) = 0; - virtual Error ReplicateAsync(UpdatesContainer &&recs, const RdxContext &ctx) = 0; - virtual void AwaitInitialSync(std::string_view nsName, const RdxContext &ctx) const = 0; - virtual void AwaitInitialSync(const RdxContext &ctx) const = 0; - virtual bool IsInitialSyncDone(std::string_view nsName) const = 0; - virtual bool IsInitialSyncDone() const = 0; - - virtual ~INsDataReplicator() = default; -}; - -} // namespace cluster -} // namespace reindexer diff --git a/cpp_src/cluster/raftmanager.cc b/cpp_src/cluster/raftmanager.cc index 9b0b00995..71b4799f5 100644 --- a/cpp_src/cluster/raftmanager.cc +++ b/cpp_src/cluster/raftmanager.cc @@ -63,7 +63,8 @@ RaftInfo::Role RaftManager::Elections() { loop_.spawn(wg, [this, &electionsStat, nodeId, term, &succeedRoutines, isDesiredLeader] { auto& node = nodes_[nodeId]; if (!node.client.Status().ok()) { - node.client.Connect(node.dsn, loop_, createConnectionOpts()); + auto err = node.client.Connect(node.dsn, loop_, createConnectionOpts()); + (void)err; // Error will be handled during the further requests } NodeData suggestion, result; suggestion.serverId = serverId_; @@ -244,14 +245,15 @@ void RaftManager::startPingRoutines() { nodes_[nodeId].hasNetworkError = false; loop_.spawn(pingWg_, [this, nodeId]() noexcept { auto& node = nodes_[nodeId]; - node.client.Connect(node.dsn, loop_); + auto err = node.client.Connect(node.dsn, loop_); + (void)err; // Error will be handled duering the further requests auto voteData = voteData_.load(); bool isFirstPing = true; while (!terminate_.load() && getRole(voteData) == RaftInfo::Role::Leader) { NodeData leader; leader.serverId = serverId_; leader.electionsTerm = getTerm(voteData); - const auto err = node.client.LeadersPing(leader); + err = node.client.LeadersPing(leader); const bool isNetworkError = (err.code() == errTimeout) || (err.code() == errNetwork); if (node.isOk != err.ok() || isNetworkError != node.hasNetworkError || isFirstPing) { node.isOk = err.ok(); diff --git a/cpp_src/cluster/replication/asyncdatareplicator.cc b/cpp_src/cluster/replication/asyncdatareplicator.cc index ed8bddd33..d34d7b78c 100644 --- a/cpp_src/cluster/replication/asyncdatareplicator.cc +++ b/cpp_src/cluster/replication/asyncdatareplicator.cc @@ -36,7 +36,6 @@ bool AsyncDataReplicator::IsExpectingStartup() const noexcept { void AsyncDataReplicator::Run() { auto localNamespaces = getLocalNamespaces(); - fast_hash_set namespaces; { std::lock_guard lck(mtx_); if (!isExpectingStartup()) { @@ -45,6 +44,9 @@ void AsyncDataReplicator::Run() { } // NOLINTBEGIN (bugprone-unchecked-optional-access) Optionals were checked in isExpectingStartup() + if (config_->nodes.size() > UpdatesQueueT::kMaxReplicas) { + throw Error(errParams, "Async replication nodes limit was reached: %d", UpdatesQueueT::kMaxReplicas); + } statsCollector_.Init(config_->nodes); log_.SetLevel(config_->logLevel); updatesQueue_.ReinitAsyncQueue(statsCollector_, std::optional(getMergedNsConfig(config_.value())), log_); @@ -67,8 +69,11 @@ void AsyncDataReplicator::Run() { if (config_->role == AsyncReplConfigData::Role::Leader) { for (auto& ns : localNamespaces) { if (!clusterizator_.NamespaceIsInClusterConfig(ns)) { - thisNode_.SetClusterizationStatus(ns, ClusterizationStatus{baseConfig_->serverID, ClusterizationStatus::Role::None}, - RdxContext()); + auto err = thisNode_.SetClusterizationStatus( + ns, ClusterizationStatus{baseConfig_->serverID, ClusterizationStatus::Role::None}, RdxContext()); + if (!err.ok()) { + logWarn("SetClusterizationStatus for the local '%s' namespace error: %s", ns, err.what()); + } } } } @@ -122,7 +127,7 @@ void AsyncDataReplicator::stop() { } } -AsyncDataReplicator::NsNamesHashSetT AsyncDataReplicator::getLocalNamespaces() { +NsNamesHashSetT AsyncDataReplicator::getLocalNamespaces() { std::vector nsDefs; NsNamesHashSetT namespaces; auto err = thisNode_.EnumNamespaces(nsDefs, EnumNamespacesOpts().OnlyNames().HideSystem().HideTemporary().WithClosed(), RdxContext()); @@ -135,7 +140,7 @@ AsyncDataReplicator::NsNamesHashSetT AsyncDataReplicator::getLocalNamespaces() { return namespaces; } -AsyncDataReplicator::NsNamesHashSetT AsyncDataReplicator::getMergedNsConfig(const AsyncReplConfigData& config) { +NsNamesHashSetT AsyncDataReplicator::getMergedNsConfig(const AsyncReplConfigData& config) { assert(config.nodes.size()); bool hasNodesWithDefaultConfig = false; for (auto& node : config.nodes) { diff --git a/cpp_src/cluster/replication/asyncdatareplicator.h b/cpp_src/cluster/replication/asyncdatareplicator.h index ec6f18e6b..8d7677882 100644 --- a/cpp_src/cluster/replication/asyncdatareplicator.h +++ b/cpp_src/cluster/replication/asyncdatareplicator.h @@ -12,8 +12,7 @@ class Clusterizator; class AsyncDataReplicator { public: - using NsNamesHashSetT = fast_hash_set; - using UpdatesQueueT = UpdatesQueuePair; + using UpdatesQueueT = UpdatesQueuePair; AsyncDataReplicator(UpdatesQueueT &, SharedSyncState<> &, ReindexerImpl &, Clusterizator &); diff --git a/cpp_src/cluster/replication/asyncreplthread.h b/cpp_src/cluster/replication/asyncreplthread.h index 4d7213ced..d3943ca0f 100644 --- a/cpp_src/cluster/replication/asyncreplthread.h +++ b/cpp_src/cluster/replication/asyncreplthread.h @@ -16,8 +16,9 @@ class AsyncThreadParam { bool IsLeader() const noexcept { return true; } void AwaitReplPermission() const noexcept {} - void OnNewNsAppearance(const std::string &) const noexcept {} + void OnNewNsAppearance(const NamespaceName &) const noexcept {} void OnUpdateReplicationFailure() const noexcept {} + bool IsNamespaceInConfig(size_t nodeId, const NamespaceName &ns) const noexcept { return (*nodes_)[nodeId].Namespaces()->IsInList(ns); } bool IsNamespaceInConfig(size_t nodeId, std::string_view ns) const noexcept { return (*nodes_)[nodeId].Namespaces()->IsInList(ns); } void OnNodeBecameUnsynchonized(uint32_t) const noexcept {} void OnAllUpdatesReplicated(uint32_t, int64_t) const noexcept {} diff --git a/cpp_src/cluster/replication/clusterdatareplicator.cc b/cpp_src/cluster/replication/clusterdatareplicator.cc index a954aa66d..69b860a7f 100644 --- a/cpp_src/cluster/replication/clusterdatareplicator.cc +++ b/cpp_src/cluster/replication/clusterdatareplicator.cc @@ -10,8 +10,8 @@ ClusterDataReplicator::ClusterDataReplicator(ClusterDataReplicator::UpdatesQueue : statsCollector_(std::string(kClusterReplStatsType)), raftManager_(loop_, statsCollector_, log_, [this](uint32_t uid, bool online) { - UpdatesContainer recs(1); - recs[0] = UpdateRecord{UpdateRecord::Type::NodeNetworkCheck, uid, online}; + UpdatesContainer recs; + recs.emplace_back(updates::URType::NodeNetworkCheck, uid, online); updatesQueue_.PushNowait(std::move(recs)); }), updatesQueue_(q), @@ -59,6 +59,9 @@ void ClusterDataReplicator::Run() { if (config_->nodes.size() && config_->nodes.size() < 3) { throw Error(errParams, "Minimal cluster size is 3, but only %d nodes were in config", config_->nodes.size()); } + if (config_->nodes.size() > UpdatesQueueT::kMaxReplicas) { + throw Error(errParams, "Sync cluster nodes limit was reached: %d", UpdatesQueueT::kMaxReplicas); + } for (auto& node : config_->nodes) { if (ids.count(node.serverId)) { throw Error(errParams, "Duplicated server id in cluster config: %d", node.serverId); diff --git a/cpp_src/cluster/replication/clusterdatareplicator.h b/cpp_src/cluster/replication/clusterdatareplicator.h index 354676e8a..f22b529c4 100644 --- a/cpp_src/cluster/replication/clusterdatareplicator.h +++ b/cpp_src/cluster/replication/clusterdatareplicator.h @@ -13,9 +13,8 @@ namespace cluster { class ClusterDataReplicator { public: - using UpdatesQueueT = UpdatesQueuePair; + using UpdatesQueueT = UpdatesQueuePair; using UpdatesQueueShardT = UpdatesQueueT::QueueT; - using NsNamesHashSetT = fast_hash_set; ClusterDataReplicator(UpdatesQueueT &, SharedSyncState<> &, ReindexerImpl &); diff --git a/cpp_src/cluster/replication/clusterreplthread.cc b/cpp_src/cluster/replication/clusterreplthread.cc index aa167fbeb..69036bc1c 100644 --- a/cpp_src/cluster/replication/clusterreplthread.cc +++ b/cpp_src/cluster/replication/clusterreplthread.cc @@ -1,14 +1,11 @@ #include "clusterreplthread.h" -#include "client/snapshot.h" -#include "core/namespace/namespacestat.h" #include "core/reindexer_impl/reindexerimpl.h" -#include "net/cproto/cproto.h" namespace reindexer { namespace cluster { ClusterReplThread::ClusterReplThread(int serverId, ReindexerImpl& thisNode, const NsNamesHashSetT* namespaces, - std::shared_ptr> q, SharedSyncState<>& syncState, + std::shared_ptr > q, SharedSyncState<>& syncState, SynchronizationList& syncList, std::function requestElectionsRestartCb, ReplicationStatsCollector statsCollector, const Logger& l) : base_(serverId, thisNode, std::move(q), diff --git a/cpp_src/cluster/replication/clusterreplthread.h b/cpp_src/cluster/replication/clusterreplthread.h index 54d7005e3..5416b78dd 100644 --- a/cpp_src/cluster/replication/clusterreplthread.h +++ b/cpp_src/cluster/replication/clusterreplthread.h @@ -8,8 +8,6 @@ namespace cluster { class ClusterThreadParam { public: - using NsNamesHashSetT = fast_hash_set; - ClusterThreadParam(const NsNamesHashSetT *namespaces, coroutine::channel &ch, SharedSyncState<> &st, SynchronizationList &syncList, std::function cb) : namespaces_(namespaces), @@ -22,12 +20,15 @@ class ClusterThreadParam { bool IsLeader() const noexcept { return !leadershipAwaitCh_.opened(); } void AwaitReplPermission() { leadershipAwaitCh_.pop(); } - void OnNewNsAppearance(const std::string &ns) { sharedSyncState_.MarkSynchronized(ns); } + void OnNewNsAppearance(const NamespaceName &ns) { sharedSyncState_.MarkSynchronized(ns); } void OnUpdateReplicationFailure() { if (sharedSyncState_.GetRolesPair().second.role == RaftInfo::Role::Leader) { requestElectionsRestartCb_(); } } + bool IsNamespaceInConfig(size_t, const NamespaceName &ns) const noexcept { + return namespaces_->empty() || (namespaces_->find(ns) != namespaces_->end()); + } bool IsNamespaceInConfig(size_t, std::string_view ns) const noexcept { return namespaces_->empty() || (namespaces_->find(ns) != namespaces_->end()); } @@ -46,11 +47,9 @@ class ClusterThreadParam { class ClusterReplThread { public: - using NsNamesHashSetT = fast_hash_set; - - ClusterReplThread(int serverId, ReindexerImpl &thisNode, const NsNamesHashSetT *, std::shared_ptr>, - SharedSyncState<> &, SynchronizationList &, std::function requestElectionsRestartCb, - ReplicationStatsCollector, const Logger &); + ClusterReplThread(int serverId, ReindexerImpl &thisNode, const NsNamesHashSetT *, + std::shared_ptr>, SharedSyncState<> &, + SynchronizationList &, std::function requestElectionsRestartCb, ReplicationStatsCollector, const Logger &); ~ClusterReplThread(); void Run(ReplThreadConfig config, std::vector> &&nodesList, size_t totalNodesCount); void SendTerminate() noexcept; diff --git a/cpp_src/cluster/replication/leadersyncer.cc b/cpp_src/cluster/replication/leadersyncer.cc index 29f988af8..9d8c4aa1d 100644 --- a/cpp_src/cluster/replication/leadersyncer.cc +++ b/cpp_src/cluster/replication/leadersyncer.cc @@ -139,7 +139,7 @@ void LeaderSyncThread::actualizeShardingConfig() { if (updated) { using CallbackT = ReindexerImpl::CallbackT; gason::JsonParser parser; - this->thisNode_.proxyCallbacks_.at({"leader_config_process", CallbackT::Type::System})(parser.Parse(span(config.GetJSON())), + this->thisNode_.proxyCallbacks_.at({"leader_config_process", CallbackT::Type::System})(parser.Parse(giftStr(config.GetJSON())), CallbackT::SourceIdT{config.sourceId}, {}); } } @@ -165,8 +165,11 @@ void LeaderSyncThread::sync() { auto tryDropTmpNamespace = [this, &tmpNsName] { if (!tmpNsName.empty()) { logError("%d: Dropping '%s'...", cfg_.serverId, tmpNsName); - thisNode_.DropNamespace(tmpNsName, RdxContext()); - logError("%d: '%s' was dropped", cfg_.serverId, tmpNsName); + if (auto err = thisNode_.DropNamespace(tmpNsName, RdxContext()); err.ok()) { + logError("%d: '%s' was dropped", cfg_.serverId, tmpNsName); + } else { + logError("%d: '%s' drop error: %s", cfg_.serverId, tmpNsName, err.what()); + } } }; try { @@ -179,7 +182,7 @@ void LeaderSyncThread::sync() { const bool fullResync = retry > 0; syncNamespaceImpl(fullResync, entry, tmpNsName); ReplicationStateV2 state; - err = thisNode_.GetReplState(tmpNsName.empty() ? entry.nsName : tmpNsName, state, RdxContext()); + err = thisNode_.GetReplState(tmpNsName.empty() ? std::string_view(entry.nsName) : tmpNsName, state, RdxContext()); if (!err.ok()) { throw err; } @@ -208,7 +211,7 @@ void LeaderSyncThread::sync() { cfg_.serverId, entry.nsName, expectedDataHash, expectedDataCount, state.dataHash, state.dataCount); tryDropTmpNamespace(); } - sharedSyncState_.MarkSynchronized(std::string(entry.nsName)); + sharedSyncState_.MarkSynchronized(entry.nsName); } catch (const Error& err) { lastError_ = err; logError("%d: Unable to sync local namespace '%s': %s", cfg_.serverId, entry.nsName, lastError_.what()); diff --git a/cpp_src/cluster/replication/leadersyncer.h b/cpp_src/cluster/replication/leadersyncer.h index 72f0ce3d3..b3c91fbb0 100644 --- a/cpp_src/cluster/replication/leadersyncer.h +++ b/cpp_src/cluster/replication/leadersyncer.h @@ -38,7 +38,7 @@ class LeaderSyncQueue { std::vector nodes; std::vector data; - std::string_view nsName; + NamespaceName nsName; ExtendedLsn latestLsn; ExtendedLsn localLsn; NodeData localData; diff --git a/cpp_src/cluster/replication/replicationthread.cc b/cpp_src/cluster/replication/replicationthread.cc index f16c798d5..966c70b26 100644 --- a/cpp_src/cluster/replication/replicationthread.cc +++ b/cpp_src/cluster/replication/replicationthread.cc @@ -1,14 +1,12 @@ #include "asyncreplthread.h" -#include "client/snapshot.h" #include "cluster/sharding/shardingcontrolrequest.h" #include "clusterreplthread.h" #include "core/defnsconfigs.h" #include "core/namespace/snapshot/snapshot.h" #include "core/reindexer_impl/reindexerimpl.h" #include "tools/catch_and_return.h" -#include "tools/flagguard.h" +#include "updates/updatesqueue.h" #include "updatesbatcher.h" -#include "updatesqueue.h" #include "vendor/spdlog/common.h" namespace reindexer { @@ -17,15 +15,28 @@ namespace cluster { constexpr auto kAwaitNsCopyInterval = std::chrono::milliseconds(2000); constexpr auto kCoro32KStackSize = 32 * 1024; +using updates::ItemReplicationRecord; +using updates::TagsMatcherReplicationRecord; +using updates::IndexReplicationRecord; +using updates::MetaReplicationRecord; +using updates::QueryReplicationRecord; +using updates::SchemaReplicationRecord; +using updates::AddNamespaceReplicationRecord; +using updates::RenameNamespaceReplicationRecord; +using updates::NodeNetworkCheckRecord; +using updates::SaveNewShardingCfgRecord; +using updates::ApplyNewShardingCfgRecord; +using updates::ResetShardingCfgRecord; + template bool UpdateApplyStatus::IsHaveToResync() const noexcept { static_assert(std::is_same_v || std::is_same_v, "Unexpected param type"); if constexpr (std::is_same_v) { - return type == UpdateRecord::Type::ResyncNamespaceGeneric || type == UpdateRecord::Type::ResyncOnUpdatesDrop; + return type == updates::URType::ResyncNamespaceGeneric || type == updates::URType::ResyncOnUpdatesDrop; } else { - return type == UpdateRecord::Type::ResyncNamespaceGeneric || type == UpdateRecord::Type::ResyncNamespaceLeaderInit || - type == UpdateRecord::Type::ResyncOnUpdatesDrop; + return type == updates::URType::ResyncNamespaceGeneric || type == updates::URType::ResyncNamespaceLeaderInit || + type == updates::URType::ResyncOnUpdatesDrop; } } @@ -206,8 +217,8 @@ void ReplThread::nodeReplicationRoutine(Node& node) { node.connObserverId = node.client.AddConnectionStateObserver([this, &node](const Error& err) noexcept { if (!err.ok() && updates_ && !terminate_) { logInfo("%d:%d Connection error: %s", serverId_, node.uid, err.what()); - UpdatesContainer recs(1); - recs[0] = UpdateRecord{UpdateRecord::Type::NodeNetworkCheck, node.uid, false}; + UpdatesContainer recs; + recs.emplace_back(updates::URType::NodeNetworkCheck, node.uid, false); node.requireResync = true; updates_->template PushAsync(std::move(recs)); } @@ -249,7 +260,8 @@ void ReplThread::nodeReplicationRoutine(Node& node) { logTrace("%d:%d Node replication routine was terminated", serverId_, node.uid); } if (node.connObserverId.has_value()) { - node.client.RemoveConnectionStateObserver(*node.connObserverId); + auto err = node.client.RemoveConnectionStateObserver(*node.connObserverId); + (void)err; // ignore; Error does not matter here node.connObserverId.reset(); } node.client.Stop(); @@ -396,15 +408,16 @@ Error ReplThread::nodeReplicationImpl(Node& node) { if (!nsExists) { replState = ReplicationStateV2(); } - err = syncNamespace(node, ns.name, replState); + const NamespaceName nsName(ns.name); + err = syncNamespace(node, nsName, replState); if (!err.ok()) { logWarn("%d:%d Namespace sync error: %s", serverId_, node.uid, err.what()); if (err.code() == errNotFound) { err = Error(); - logWarn("%d:%d Expecting drop namespace record for '%s'", serverId_, node.uid, ns.name); + logWarn("%d:%d Expecting drop namespace record for '%s'", serverId_, node.uid, nsName); } else if (err.code() == errDataHashMismatch) { replState = ReplicationStateV2(); - err = syncNamespace(node, ns.name, replState); + err = syncNamespace(node, nsName, replState); if (!err.ok()) { logWarn("%d:%d Namespace sync error (resync due to datahash missmatch): %s", serverId_, node.uid, err.what()); @@ -460,33 +473,37 @@ void ReplThread::terminateNotifier() noexcept { template std::tuple ReplThread::handleNetworkCheckRecord(Node& node, UpdatesQueueT::UpdatePtr& updPtr, uint16_t offset, bool currentlyOnline, - const UpdateRecord& rec) noexcept { + const updates::UpdateRecord& rec) noexcept { bool hadActualNetworkCheck = false; - auto& data = std::get>(rec.data); - if (node.uid == data->nodeUid) { + auto& data = std::get(*rec.Data()); + if (node.uid == data.nodeUid) { Error err; - if (data->online != currentlyOnline) { + if (data.online != currentlyOnline) { logTrace("%d:%d: Checking network...", serverId_, node.uid); err = node.client.WithTimeout(kStatusCmdTimeout).Status(true); hadActualNetworkCheck = true; } - updPtr->OnUpdateReplicated(node.uid, consensusCnt_, requiredReplicas_, offset, false, Error()); - return std::make_tuple(hadActualNetworkCheck, UpdateApplyStatus(std::move(err), UpdateRecord::Type::NodeNetworkCheck)); + updPtr->OnUpdateHandled(node.uid, consensusCnt_, requiredReplicas_, offset, false, Error()); + return std::make_tuple(hadActualNetworkCheck, UpdateApplyStatus(std::move(err), updates::URType::NodeNetworkCheck)); } - updPtr->OnUpdateReplicated(node.uid, consensusCnt_, requiredReplicas_, offset, false, Error()); - return std::make_tuple(hadActualNetworkCheck, UpdateApplyStatus(Error(), UpdateRecord::Type::NodeNetworkCheck)); + updPtr->OnUpdateHandled(node.uid, consensusCnt_, requiredReplicas_, offset, false, Error()); + return std::make_tuple(hadActualNetworkCheck, UpdateApplyStatus(Error(), updates::URType::NodeNetworkCheck)); } template -Error ReplThread::syncNamespace(Node& node, const std::string& nsName, const ReplicationStateV2& followerState) { +Error ReplThread::syncNamespace(Node& node, const NamespaceName& nsName, const ReplicationStateV2& followerState) { try { class TmpNsGuard { public: TmpNsGuard(client::CoroReindexer& client, int serverId, const Logger& log) : client_(client), serverId_(serverId), log_(log) {} ~TmpNsGuard() { if (tmpNsName.size()) { - logWarn("%d: Removing tmp ns on error: %s", serverId_, tmpNsName); - client_.WithLSN(lsn_t(0, serverId_)).DropNamespace(tmpNsName); + logWarn("%d: Dropping tmp ns on error: '%s'", serverId_, tmpNsName); + if (auto err = client_.WithLSN(lsn_t(0, serverId_)).DropNamespace(tmpNsName); err.ok()) { + logWarn("%d: '%s' was dropped", serverId_, tmpNsName); + } else { + logWarn("%d: '%s' drop error: %s", serverId_, tmpNsName, err.what()); + } } } @@ -659,12 +676,12 @@ UpdateApplyStatus ReplThread::nodeUpdatesHandlingLoop(Node& nod auto applyUpdateF = [this, &node](const UpdatesQueueT::UpdateT::Value& upd, Context& ctx) { auto& it = upd.Data(); log_.Trace([&] { - auto& nsName = it.GetNsName(); + auto& nsName = it.NsName(); auto& nsData = *ctx.nsData; rtfmt( "%d:%d:%s Applying update with type %d (batched), id: %d, ns version: %d, lsn: %d, last synced ns " "version: %d, last synced lsn: %d", - serverId_, node.uid, nsName, int(it.type), ctx.updPtr->ID() + ctx.offset, it.extLsn.NsVersion(), it.extLsn.LSN(), + serverId_, node.uid, nsName, int(it.Type()), ctx.updPtr->ID() + ctx.offset, it.ExtLSN().NsVersion(), it.ExtLSN().LSN(), nsData.latestLsn.NsVersion(), nsData.latestLsn.LSN()); }); return applyUpdate(it, node, *ctx.nsData); @@ -675,18 +692,18 @@ UpdateApplyStatus ReplThread::nodeUpdatesHandlingLoop(Node& nod ctx.nsData->UpdateLsnOnRecord(it); log_.Trace([&] { const auto counters = upd.GetCounters(); - rtfmt("%d:%d:%s Apply update (lsn: %d, id: %d) result: %s. Replicas: %d", serverId_, node.uid, it.GetNsName(), it.extLsn.LSN(), + rtfmt("%d:%d:%s Apply update (lsn: %d, id: %d) result: %s. Replicas: %d", serverId_, node.uid, it.NsName(), it.ExtLSN().LSN(), ctx.updPtr->ID() + ctx.offset, (res.err.ok() ? "OK" : "ERROR:" + res.err.what()), counters.replicas + 1); }); - const auto replRes = ctx.updPtr->OnUpdateReplicated(node.uid, consensusCnt_, requiredReplicas_, ctx.offset, - it.emmiterServerId == node.serverId, res.err); + const auto replRes = ctx.updPtr->OnUpdateHandled(node.uid, consensusCnt_, requiredReplicas_, ctx.offset, + it.EmmiterServerID() == node.serverId, res.err); if (res.err.ok()) { bhvParam_.OnUpdateSucceed(node.uid, ctx.updPtr->ID() + ctx.offset); } - requireReelections = requireReelections || (replRes == ReplicationResult::Error); + requireReelections = requireReelections || (replRes == updates::ReplicationResult::Error); }; auto convertResF = [](Error&& err, const UpdatesQueueT::UpdateT::Value& upd) { - return UpdateApplyStatus(std::move(err), upd.Data().type); + return UpdateApplyStatus(std::move(err), upd.Data().Type()); }; UpdatesBatcher batcher( loop, config_.BatchingRoutinesCount, std::move(applyUpdateF), std::move(onUpdateResF), std::move(convertResF)); @@ -713,7 +730,7 @@ UpdateApplyStatus ReplThread::nodeUpdatesHandlingLoop(Node& nod nextUpdateID); node.nextUpdateId = nextUpdateID; statsCollector_.OnUpdateApplied(node.uid, updatePtr->ID()); - return UpdateApplyStatus(Error(), UpdateRecord::Type::ResyncOnUpdatesDrop); + return UpdateApplyStatus(Error(), updates::URType::ResyncOnUpdatesDrop); } logTrace("%d:%d Got new update. Next update id: %d. Queue block id: %d, block count: %d", serverId_, node.uid, node.nextUpdateId, updatePtr->ID(), updatePtr->Count()); @@ -734,34 +751,34 @@ UpdateApplyStatus ReplThread::nodeUpdatesHandlingLoop(Node& nod } continue; } - const std::string& nsName = it.GetNsName(); + const auto& nsName = it.NsName(); if constexpr (!isClusterReplThread()) { if (!bhvParam_.IsNamespaceInConfig(node.uid, nsName)) { - updatePtr->OnUpdateReplicated(node.uid, consensusCnt_, requiredReplicas_, offset, false, Error()); + updatePtr->OnUpdateHandled(node.uid, consensusCnt_, requiredReplicas_, offset, false, Error()); bhvParam_.OnUpdateSucceed(node.uid, updatePtr->ID() + offset); continue; } } - if (it.type == UpdateRecord::Type::AddNamespace) { + if (it.Type() == updates::URType::AddNamespace) { bhvParam_.OnNewNsAppearance(nsName); } auto& nsData = node.namespaceData[nsName]; - const bool isOutdatedRecord = !it.extLsn.HasNewerCounterThan(nsData.latestLsn) || nsData.latestLsn.IsEmpty(); + const bool isOutdatedRecord = !it.ExtLSN().HasNewerCounterThan(nsData.latestLsn) || nsData.latestLsn.IsEmpty(); if ((!it.IsDbRecord() && isOutdatedRecord) || it.IsEmptyRecord()) { logTrace( "%d:%d:%s Skipping update with type %d, id: %d, ns version: %d, lsn: %d, last synced ns " "version: %d, last synced lsn: %d", - serverId_, node.uid, nsName, int(it.type), updatePtr->ID() + offset, it.extLsn.NsVersion(), it.extLsn.LSN(), + serverId_, node.uid, nsName, int(it.Type()), updatePtr->ID() + offset, it.ExtLSN().NsVersion(), it.ExtLSN().LSN(), nsData.latestLsn.NsVersion(), nsData.latestLsn.LSN()); - updatePtr->OnUpdateReplicated(node.uid, consensusCnt_, requiredReplicas_, offset, it.emmiterServerId == node.serverId, - Error()); + updatePtr->OnUpdateHandled(node.uid, consensusCnt_, requiredReplicas_, offset, it.EmmiterServerID() == node.serverId, + Error()); continue; } if (nsData.tx.IsFree() && it.IsRequiringTx()) { res = UpdateApplyStatus(Error(errTxDoesNotExist, "Update requires tx. ID: %d, lsn: %d, type: %d", - updatePtr->ID() + offset, it.extLsn.LSN(), int(it.type))); + updatePtr->ID() + offset, it.ExtLSN().LSN(), int(it.Type()))); --node.nextUpdateId; // Have to read this update again break; } @@ -795,21 +812,21 @@ UpdateApplyStatus ReplThread::nodeUpdatesHandlingLoop(Node& nod "%d:%d:%s Applying update with type %d (no batching), id: %d, ns version: %d, lsn: %d, " "last synced ns " "version: %d, last synced lsn: %d", - serverId_, node.uid, nsName, int(it.type), updatePtr->ID() + offset, it.extLsn.NsVersion(), it.extLsn.LSN(), + serverId_, node.uid, nsName, int(it.Type()), updatePtr->ID() + offset, it.ExtLSN().NsVersion(), it.ExtLSN().LSN(), nsData.latestLsn.NsVersion(), nsData.latestLsn.LSN()); res = applyUpdate(it, node, nsData); logTrace("%d:%d:%s Apply update result (id: %d, ns version: %d, lsn: %d): %s. Replicas: %d", serverId_, node.uid, - nsName, updatePtr->ID() + offset, it.extLsn.NsVersion(), it.extLsn.LSN(), + nsName, updatePtr->ID() + offset, it.ExtLSN().NsVersion(), it.ExtLSN().LSN(), (res.err.ok() ? "OK" : "ERROR:" + res.err.what()), upd.GetCounters().replicas + 1); - const auto replRes = updatePtr->OnUpdateReplicated(node.uid, consensusCnt_, requiredReplicas_, offset, - it.emmiterServerId == node.serverId, res.err); + const auto replRes = updatePtr->OnUpdateHandled(node.uid, consensusCnt_, requiredReplicas_, offset, + it.EmmiterServerID() == node.serverId, res.err); if (res.err.ok()) { nsData.UpdateLsnOnRecord(it); bhvParam_.OnUpdateSucceed(node.uid, updatePtr->ID() + offset); nsData.requiresTmUpdate = it.IsRequiringTmUpdate(); } else { - requireReelections = requireReelections || (replRes == ReplicationResult::Error); + requireReelections = requireReelections || (replRes == updates::ReplicationResult::Error); } } @@ -881,13 +898,13 @@ bool ReplThread::handleUpdatesWithError(Node& node, const Error } continue; } - const std::string& nsName = it.GetNsName(); + const auto& nsName = it.NsName(); if (!bhvParam_.IsNamespaceInConfig(node.uid, nsName)) continue; - if (it.type == UpdateRecord::Type::AddNamespace || it.type == UpdateRecord::Type::DropNamespace) { + if (it.Type() == updates::URType::AddNamespace || it.Type() == updates::URType::DropNamespace) { node.namespaceData[nsName].isClosed = false; bhvParam_.OnNewNsAppearance(nsName); - } else if (it.type == UpdateRecord::Type::CloseNamespace) { + } else if (it.Type() == updates::URType::CloseNamespace) { node.namespaceData[nsName].isClosed = true; } @@ -896,17 +913,17 @@ bool ReplThread::handleUpdatesWithError(Node& node, const Error break; } - assert(it.emmiterServerId != serverId_); - const bool isEmmiter = it.emmiterServerId == node.serverId; + assert(it.EmmiterServerID() != serverId_); + const bool isEmmiter = it.EmmiterServerID() == node.serverId; if (isEmmiter) { --node.nextUpdateId; return true; // Retry sync after receiving update from offline node } - const auto replRes = updatePtr->OnUpdateReplicated( + const auto replRes = updatePtr->OnUpdateHandled( node.uid, consensusCnt_, requiredReplicas_, offset, isEmmiter, Error(errUpdateReplication, "Unable to send update to enough amount of replicas. Last error: %s", err.what())); - if (replRes == ReplicationResult::Error && !hadErrorOnLastUpdate) { + if (replRes == updates::ReplicationResult::Error && !hadErrorOnLastUpdate) { hadErrorOnLastUpdate = true; logWarn("%d:%d Requesting leader reelection on error: %s", serverId_, node.uid, err.what()); bhvParam_.OnUpdateReplicationFailure(); @@ -919,8 +936,8 @@ bool ReplThread::handleUpdatesWithError(Node& node, const Error "Required: " "%d, succeed: " "%d, failed: %d, replicas: %d", - serverId_, node.uid, nsName, err.what(), int(it.type), it.extLsn.NsVersion(), it.extLsn.LSN(), - (isEmmiter ? node.serverId : it.emmiterServerId), consensusCnt_, counters.approves, counters.errors, + serverId_, node.uid, nsName, err.what(), int(it.Type()), it.ExtLSN().NsVersion(), it.ExtLSN().LSN(), + (isEmmiter ? node.serverId : it.EmmiterServerID()), consensusCnt_, counters.approves, counters.errors, counters.replicas + 1); }); } @@ -949,7 +966,7 @@ Error ReplThread::checkIfReplicationAllowed(Node& node, LogLeve if (!err.ok()) return err; ReplicationStats stats; - err = stats.FromJSON(wser.Slice()); + err = stats.FromJSON(giftStr(wser.Slice())); if (!err.ok()) return err; if (stats.nodeStats.size()) { @@ -978,168 +995,168 @@ Error ReplThread::checkIfReplicationAllowed(Node& node, LogLeve } template -UpdateApplyStatus ReplThread::applyUpdate(const UpdateRecord& rec, ReplThread::Node& node, +UpdateApplyStatus ReplThread::applyUpdate(const updates::UpdateRecord& rec, ReplThread::Node& node, ReplThread::NamespaceData& nsData) noexcept { - auto lsn = rec.extLsn.LSN(); - std::string_view nsName = rec.GetNsName(); + auto lsn = rec.ExtLSN().LSN(); + std::string_view nsName = rec.NsName(); auto& client = node.client; try { - switch (rec.type) { - case UpdateRecord::Type::ItemUpdate: { - auto& data = std::get>(rec.data); - return UpdateApplyStatus(client.WithLSN(lsn).Update(nsName, data->cjson.Slice()), rec.type); + switch (rec.Type()) { + case updates::URType::ItemUpdate: { + auto& data = std::get(*rec.Data()); + return UpdateApplyStatus(client.WithLSN(lsn).Update(nsName, data.cjson.Slice()), rec.Type()); } - case UpdateRecord::Type::ItemUpsert: { - auto& data = std::get>(rec.data); - return UpdateApplyStatus(client.WithLSN(lsn).Upsert(nsName, data->cjson.Slice()), rec.type); + case updates::URType::ItemUpsert: { + auto& data = std::get(*rec.Data()); + return UpdateApplyStatus(client.WithLSN(lsn).Upsert(nsName, data.cjson.Slice()), rec.Type()); } - case UpdateRecord::Type::ItemDelete: { - auto& data = std::get>(rec.data); - return UpdateApplyStatus(client.WithLSN(lsn).Delete(nsName, data->cjson.Slice()), rec.type); + case updates::URType::ItemDelete: { + auto& data = std::get(*rec.Data()); + return UpdateApplyStatus(client.WithLSN(lsn).Delete(nsName, data.cjson.Slice()), rec.Type()); } - case UpdateRecord::Type::ItemInsert: { - auto& data = std::get>(rec.data); - return UpdateApplyStatus(client.WithLSN(lsn).Insert(nsName, data->cjson.Slice()), rec.type); + case updates::URType::ItemInsert: { + auto& data = std::get(*rec.Data()); + return UpdateApplyStatus(client.WithLSN(lsn).Insert(nsName, data.cjson.Slice()), rec.Type()); } - case UpdateRecord::Type::IndexAdd: { - auto& data = std::get>(rec.data); - return UpdateApplyStatus(client.WithLSN(lsn).AddIndex(nsName, data->idef), rec.type); + case updates::URType::IndexAdd: { + auto& data = std::get(*rec.Data()); + return UpdateApplyStatus(client.WithLSN(lsn).AddIndex(nsName, data.idef), rec.Type()); } - case UpdateRecord::Type::IndexDrop: { - auto& data = std::get>(rec.data); - return UpdateApplyStatus(client.WithLSN(lsn).DropIndex(nsName, data->idef), rec.type); + case updates::URType::IndexDrop: { + auto& data = std::get(*rec.Data()); + return UpdateApplyStatus(client.WithLSN(lsn).DropIndex(nsName, data.idef), rec.Type()); } - case UpdateRecord::Type::IndexUpdate: { - auto& data = std::get>(rec.data); - return UpdateApplyStatus(client.WithLSN(lsn).UpdateIndex(nsName, data->idef), rec.type); + case updates::URType::IndexUpdate: { + auto& data = std::get(*rec.Data()); + return UpdateApplyStatus(client.WithLSN(lsn).UpdateIndex(nsName, data.idef), rec.Type()); } - case UpdateRecord::Type::PutMeta: { - auto& data = std::get>(rec.data); - return UpdateApplyStatus(client.WithLSN(lsn).PutMeta(nsName, data->key, data->value), rec.type); + case updates::URType::PutMeta: { + auto& data = std::get(*rec.Data()); + return UpdateApplyStatus(client.WithLSN(lsn).PutMeta(nsName, data.key, data.value), rec.Type()); } - case UpdateRecord::Type::PutMetaTx: { + case updates::URType::PutMetaTx: { if (nsData.tx.IsFree()) { - return UpdateApplyStatus(Error(errLogic, "Tx is empty"), rec.type); + return UpdateApplyStatus(Error(errLogic, "Tx is empty"), rec.Type()); } - auto& data = std::get>(rec.data); - return UpdateApplyStatus(nsData.tx.PutMeta(data->key, data->value, lsn), rec.type); + auto& data = std::get(*rec.Data()); + return UpdateApplyStatus(nsData.tx.PutMeta(data.key, data.value, lsn), rec.Type()); } - case UpdateRecord::Type::DeleteMeta: { - auto& data = std::get>(rec.data); - return UpdateApplyStatus(client.WithLSN(lsn).DeleteMeta(nsName, data->key), rec.type); + case updates::URType::DeleteMeta: { + auto& data = std::get(*rec.Data()); + return UpdateApplyStatus(client.WithLSN(lsn).DeleteMeta(nsName, data.key), rec.Type()); } - case UpdateRecord::Type::UpdateQuery: { - auto& data = std::get>(rec.data); + case updates::URType::UpdateQuery: { + auto& data = std::get(*rec.Data()); client::CoroQueryResults qr; - return UpdateApplyStatus(client.WithLSN(lsn).Update(Query::FromSQL(data->sql), qr), rec.type); + return UpdateApplyStatus(client.WithLSN(lsn).Update(Query::FromSQL(data.sql), qr), rec.Type()); } - case UpdateRecord::Type::DeleteQuery: { - auto& data = std::get>(rec.data); + case updates::URType::DeleteQuery: { + auto& data = std::get(*rec.Data()); client::CoroQueryResults qr; - return UpdateApplyStatus(client.WithLSN(lsn).Delete(Query::FromSQL(data->sql), qr), rec.type); + return UpdateApplyStatus(client.WithLSN(lsn).Delete(Query::FromSQL(data.sql), qr), rec.Type()); } - case UpdateRecord::Type::SetSchema: { - auto& data = std::get>(rec.data); - return UpdateApplyStatus(client.WithLSN(lsn).SetSchema(nsName, data->schema), rec.type); + case updates::URType::SetSchema: { + auto& data = std::get(*rec.Data()); + return UpdateApplyStatus(client.WithLSN(lsn).SetSchema(nsName, data.schema), rec.Type()); } - case UpdateRecord::Type::Truncate: { - return UpdateApplyStatus(client.WithLSN(lsn).TruncateNamespace(nsName), rec.type); + case updates::URType::Truncate: { + return UpdateApplyStatus(client.WithLSN(lsn).TruncateNamespace(nsName), rec.Type()); } - case UpdateRecord::Type::BeginTx: { + case updates::URType::BeginTx: { if (!nsData.tx.IsFree()) { - return UpdateApplyStatus(Error(errLogic, "Tx is not empty"), rec.type); + return UpdateApplyStatus(Error(errLogic, "Tx is not empty"), rec.Type()); } nsData.tx = node.client.WithLSN(lsn).NewTransaction(nsName); - return UpdateApplyStatus(Error(nsData.tx.Status()), rec.type); + return UpdateApplyStatus(Error(nsData.tx.Status()), rec.Type()); } - case UpdateRecord::Type::CommitTx: { + case updates::URType::CommitTx: { if (nsData.tx.IsFree()) { - return UpdateApplyStatus(Error(errLogic, "Tx is empty"), rec.type); + return UpdateApplyStatus(Error(errLogic, "Tx is empty"), rec.Type()); } client::CoroQueryResults qr; - return UpdateApplyStatus(node.client.WithLSN(lsn).CommitTransaction(nsData.tx, qr), rec.type); + return UpdateApplyStatus(node.client.WithLSN(lsn).CommitTransaction(nsData.tx, qr), rec.Type()); } - case UpdateRecord::Type::ItemUpdateTx: - case UpdateRecord::Type::ItemUpsertTx: - case UpdateRecord::Type::ItemDeleteTx: - case UpdateRecord::Type::ItemInsertTx: { + case updates::URType::ItemUpdateTx: + case updates::URType::ItemUpsertTx: + case updates::URType::ItemDeleteTx: + case updates::URType::ItemInsertTx: { if (nsData.tx.IsFree()) { - return UpdateApplyStatus(Error(errLogic, "Tx is empty"), rec.type); + return UpdateApplyStatus(Error(errLogic, "Tx is empty"), rec.Type()); } - auto& data = std::get>(rec.data); - switch (rec.type) { - case UpdateRecord::Type::ItemUpdateTx: - return UpdateApplyStatus(nsData.tx.Update(data->cjson.Slice(), lsn), rec.type); - case UpdateRecord::Type::ItemUpsertTx: - return UpdateApplyStatus(nsData.tx.Upsert(data->cjson.Slice(), lsn), rec.type); - case UpdateRecord::Type::ItemDeleteTx: - return UpdateApplyStatus(nsData.tx.Delete(data->cjson.Slice(), lsn), rec.type); - case UpdateRecord::Type::ItemInsertTx: - return UpdateApplyStatus(nsData.tx.Insert(data->cjson.Slice(), lsn), rec.type); - case UpdateRecord::Type::None: - case UpdateRecord::Type::ItemUpdate: - case UpdateRecord::Type::ItemUpsert: - case UpdateRecord::Type::ItemDelete: - case UpdateRecord::Type::ItemInsert: - case UpdateRecord::Type::IndexAdd: - case UpdateRecord::Type::IndexDrop: - case UpdateRecord::Type::IndexUpdate: - case UpdateRecord::Type::PutMeta: - case UpdateRecord::Type::PutMetaTx: - case UpdateRecord::Type::DeleteMeta: - case UpdateRecord::Type::UpdateQuery: - case UpdateRecord::Type::DeleteQuery: - case UpdateRecord::Type::UpdateQueryTx: - case UpdateRecord::Type::DeleteQueryTx: - case UpdateRecord::Type::SetSchema: - case UpdateRecord::Type::Truncate: - case UpdateRecord::Type::BeginTx: - case UpdateRecord::Type::CommitTx: - case UpdateRecord::Type::AddNamespace: - case UpdateRecord::Type::DropNamespace: - case UpdateRecord::Type::CloseNamespace: - case UpdateRecord::Type::RenameNamespace: - case UpdateRecord::Type::ResyncNamespaceGeneric: - case UpdateRecord::Type::ResyncNamespaceLeaderInit: - case UpdateRecord::Type::ResyncOnUpdatesDrop: - case UpdateRecord::Type::EmptyUpdate: - case UpdateRecord::Type::NodeNetworkCheck: - case UpdateRecord::Type::SetTagsMatcher: - case UpdateRecord::Type::SetTagsMatcherTx: - case UpdateRecord::Type::SaveShardingConfig: - case UpdateRecord::Type::ApplyShardingConfig: - case UpdateRecord::Type::ResetOldShardingConfig: - case UpdateRecord::Type::ResetCandidateConfig: - case UpdateRecord::Type::RollbackCandidateConfig: + auto& data = std::get(*rec.Data()); + switch (rec.Type()) { + case updates::URType::ItemUpdateTx: + return UpdateApplyStatus(nsData.tx.Update(data.cjson.Slice(), lsn), rec.Type()); + case updates::URType::ItemUpsertTx: + return UpdateApplyStatus(nsData.tx.Upsert(data.cjson.Slice(), lsn), rec.Type()); + case updates::URType::ItemDeleteTx: + return UpdateApplyStatus(nsData.tx.Delete(data.cjson.Slice(), lsn), rec.Type()); + case updates::URType::ItemInsertTx: + return UpdateApplyStatus(nsData.tx.Insert(data.cjson.Slice(), lsn), rec.Type()); + case updates::URType::None: + case updates::URType::ItemUpdate: + case updates::URType::ItemUpsert: + case updates::URType::ItemDelete: + case updates::URType::ItemInsert: + case updates::URType::IndexAdd: + case updates::URType::IndexDrop: + case updates::URType::IndexUpdate: + case updates::URType::PutMeta: + case updates::URType::PutMetaTx: + case updates::URType::DeleteMeta: + case updates::URType::UpdateQuery: + case updates::URType::DeleteQuery: + case updates::URType::UpdateQueryTx: + case updates::URType::DeleteQueryTx: + case updates::URType::SetSchema: + case updates::URType::Truncate: + case updates::URType::BeginTx: + case updates::URType::CommitTx: + case updates::URType::AddNamespace: + case updates::URType::DropNamespace: + case updates::URType::CloseNamespace: + case updates::URType::RenameNamespace: + case updates::URType::ResyncNamespaceGeneric: + case updates::URType::ResyncNamespaceLeaderInit: + case updates::URType::ResyncOnUpdatesDrop: + case updates::URType::EmptyUpdate: + case updates::URType::NodeNetworkCheck: + case updates::URType::SetTagsMatcher: + case updates::URType::SetTagsMatcherTx: + case updates::URType::SaveShardingConfig: + case updates::URType::ApplyShardingConfig: + case updates::URType::ResetOldShardingConfig: + case updates::URType::ResetCandidateConfig: + case updates::URType::RollbackCandidateConfig: break; } std::abort(); } - case UpdateRecord::Type::UpdateQueryTx: - case UpdateRecord::Type::DeleteQueryTx: { + case updates::URType::UpdateQueryTx: + case updates::URType::DeleteQueryTx: { if (nsData.tx.IsFree()) { - return UpdateApplyStatus(Error(errLogic, "Tx is empty"), rec.type); + return UpdateApplyStatus(Error(errLogic, "Tx is empty"), rec.Type()); } - auto& data = std::get>(rec.data); - return UpdateApplyStatus(nsData.tx.Modify(Query::FromSQL(data->sql), lsn), rec.type); + auto& data = std::get(*rec.Data()); + return UpdateApplyStatus(nsData.tx.Modify(Query::FromSQL(data.sql), lsn), rec.Type()); } - case UpdateRecord::Type::SetTagsMatcherTx: { + case updates::URType::SetTagsMatcherTx: { if (nsData.tx.IsFree()) { - return UpdateApplyStatus(Error(errLogic, "Tx is empty"), rec.type); + return UpdateApplyStatus(Error(errLogic, "Tx is empty"), rec.Type()); } - auto& data = std::get>(rec.data); - TagsMatcher tm = data->tm; - return UpdateApplyStatus(nsData.tx.SetTagsMatcher(std::move(tm), lsn), rec.type); + auto& data = std::get(*rec.Data()); + TagsMatcher tm = data.tm; + return UpdateApplyStatus(nsData.tx.SetTagsMatcher(std::move(tm), lsn), rec.Type()); } - case UpdateRecord::Type::AddNamespace: { - auto& data = std::get>(rec.data); - const auto sid = rec.extLsn.NsVersion().Server(); + case updates::URType::AddNamespace: { + auto& data = std::get(*rec.Data()); + const auto sid = rec.ExtLSN().NsVersion().Server(); auto err = - client.WithLSN(lsn_t(0, sid)).AddNamespace(data->def, NsReplicationOpts{{data->stateToken}, rec.extLsn.NsVersion()}); + client.WithLSN(lsn_t(0, sid)).AddNamespace(data.def, NsReplicationOpts{{data.stateToken}, rec.ExtLSN().NsVersion()}); if (err.ok() && nsData.isClosed) { nsData.isClosed = false; logTrace("%d:%d:%s Namespace is closed on leader. Scheduling resync for followers", serverId_, node.uid, nsName); - return UpdateApplyStatus(Error(), UpdateRecord::Type::ResyncNamespaceGeneric); // Perform resync on ns reopen + return UpdateApplyStatus(Error(), updates::URType::ResyncNamespaceGeneric); // Perform resync on ns reopen } nsData.isClosed = false; if constexpr (!isClusterReplThread()) { @@ -1148,79 +1165,78 @@ UpdateApplyStatus ReplThread::applyUpdate(const UpdateRecord& r ClusterizationStatus{serverId_, ClusterizationStatus::Role::SimpleReplica}); } } - return UpdateApplyStatus(std::move(err), rec.type); + return UpdateApplyStatus(std::move(err), rec.Type()); } - case UpdateRecord::Type::DropNamespace: { + case updates::URType::DropNamespace: { lsn.SetServer(serverId_); auto err = client.WithLSN(lsn).DropNamespace(nsName); nsData.isClosed = false; if (!err.ok() && err.code() == errNotFound) { - return UpdateApplyStatus(Error(), rec.type); + return UpdateApplyStatus(Error(), rec.Type()); } - return UpdateApplyStatus(std::move(err), rec.type); + return UpdateApplyStatus(std::move(err), rec.Type()); } - case UpdateRecord::Type::CloseNamespace: { + case updates::URType::CloseNamespace: { nsData.isClosed = true; logTrace("%d:%d:%s Namespace was closed on leader", serverId_, node.uid, nsName); - return UpdateApplyStatus(Error(), rec.type); + return UpdateApplyStatus(Error(), rec.Type()); } - case UpdateRecord::Type::RenameNamespace: { + case updates::URType::RenameNamespace: { assert(false); // TODO: Rename is not supported yet - // auto& data = std::get>(rec.data); + // auto& data = std::get(*rec.data); // lsn.SetServer(serverId); // return client.WithLSN(lsn).RenameNamespace(nsName, data->dstNsName); - return UpdateApplyStatus(Error(), rec.type); + return UpdateApplyStatus(Error(), rec.Type()); } - case UpdateRecord::Type::ResyncNamespaceGeneric: - case UpdateRecord::Type::ResyncNamespaceLeaderInit: - return UpdateApplyStatus(Error(), rec.type); - case UpdateRecord::Type::SetTagsMatcher: { - auto& data = std::get>(rec.data); - TagsMatcher tm = data->tm; - return UpdateApplyStatus(client.WithLSN(lsn).SetTagsMatcher(nsName, std::move(tm)), rec.type); + case updates::URType::ResyncNamespaceGeneric: + case updates::URType::ResyncNamespaceLeaderInit: + return UpdateApplyStatus(Error(), rec.Type()); + case updates::URType::SetTagsMatcher: { + auto& data = std::get(*rec.Data()); + TagsMatcher tm = data.tm; + return UpdateApplyStatus(client.WithLSN(lsn).SetTagsMatcher(nsName, std::move(tm)), rec.Type()); } - case UpdateRecord::Type::SaveShardingConfig: { - auto& data = std::get>(rec.data); + case updates::URType::SaveShardingConfig: { + auto& data = std::get(*rec.Data()); auto err = client.WithLSN(lsn_t(0, serverId_)) .ShardingControlRequest(sharding::MakeRequestData( - data->config, data->sourceId)); - return UpdateApplyStatus(std::move(err), rec.type); + data.config, data.sourceId)); + return UpdateApplyStatus(std::move(err), rec.Type()); } - case UpdateRecord::Type::ApplyShardingConfig: { - auto& data = std::get>(rec.data); + case updates::URType::ApplyShardingConfig: { + auto& data = std::get(*rec.Data()); auto err = client.WithLSN(lsn_t(0, serverId_)) .ShardingControlRequest( - sharding::MakeRequestData(data->sourceId)); - return UpdateApplyStatus(std::move(err), rec.type); + sharding::MakeRequestData(data.sourceId)); + return UpdateApplyStatus(std::move(err), rec.Type()); } - case UpdateRecord::Type::ResetOldShardingConfig: { - auto& data = std::get>(rec.data); + case updates::URType::ResetOldShardingConfig: { + auto& data = std::get(*rec.Data()); auto err = client.WithLSN(lsn_t(0, serverId_)) .ShardingControlRequest( - sharding::MakeRequestData(data->sourceId)); - return UpdateApplyStatus(std::move(err), rec.type); + sharding::MakeRequestData(data.sourceId)); + return UpdateApplyStatus(std::move(err), rec.Type()); } - case UpdateRecord::Type::ResetCandidateConfig: { - auto& data = std::get>(rec.data); + case updates::URType::ResetCandidateConfig: { + auto& data = std::get(*rec.Data()); auto err = client.WithLSN(lsn_t(0, serverId_)) .ShardingControlRequest( - sharding::MakeRequestData(data->sourceId)); - return UpdateApplyStatus(std::move(err), rec.type); + sharding::MakeRequestData(data.sourceId)); + return UpdateApplyStatus(std::move(err), rec.Type()); } - case UpdateRecord::Type::RollbackCandidateConfig: { - auto& data = std::get>(rec.data); - auto err = - client.WithLSN(lsn_t(0, serverId_)) - .ShardingControlRequest( - sharding::MakeRequestData(data->sourceId)); - return UpdateApplyStatus(std::move(err), rec.type); + case updates::URType::RollbackCandidateConfig: { + auto& data = std::get(*rec.Data()); + auto err = client.WithLSN(lsn_t(0, serverId_)) + .ShardingControlRequest( + sharding::MakeRequestData(data.sourceId)); + return UpdateApplyStatus(std::move(err), rec.Type()); } - case UpdateRecord::Type::None: - case UpdateRecord::Type::EmptyUpdate: - case UpdateRecord::Type::ResyncOnUpdatesDrop: - case UpdateRecord::Type::NodeNetworkCheck: + case updates::URType::None: + case updates::URType::EmptyUpdate: + case updates::URType::ResyncOnUpdatesDrop: + case updates::URType::NodeNetworkCheck: std::abort(); } } catch (std::bad_variant_access& e) { diff --git a/cpp_src/cluster/replication/replicationthread.h b/cpp_src/cluster/replication/replicationthread.h index cdb0fc303..f5d61957a 100644 --- a/cpp_src/cluster/replication/replicationthread.h +++ b/cpp_src/cluster/replication/replicationthread.h @@ -3,13 +3,14 @@ #include #include "client/cororeindexer.h" #include "cluster/config.h" +#include "cluster/logger.h" #include "cluster/stats/relicationstatscollector.h" -#include "cluster/updaterecord.h" #include "core/dbconfig.h" #include "coroutine/tokens_pool.h" #include "net/ev/ev.h" #include "sharedsyncstate.h" -#include "updatesqueue.h" +#include "updates/updaterecord.h" +#include "updates/updatesqueue.h" namespace reindexer { @@ -65,31 +66,31 @@ struct ReplThreadConfig { }; struct UpdateApplyStatus { - UpdateApplyStatus(Error &&_err = Error(), UpdateRecord::Type _type = UpdateRecord::Type::None) : err(std::move(_err)), type(_type) {} + UpdateApplyStatus(Error &&_err = Error(), updates::URType _type = updates::URType::None) noexcept : err(std::move(_err)), type(_type) {} template bool IsHaveToResync() const noexcept; Error err; - UpdateRecord::Type type; + updates::URType type; }; template class ReplThread { public: - using UpdatesQueueT = UpdatesQueue; + using UpdatesQueueT = updates::UpdatesQueue; using UpdatesChT = coroutine::channel; class NamespaceData { public: - void UpdateLsnOnRecord(const UpdateRecord &rec) { + void UpdateLsnOnRecord(const updates::UpdateRecord &rec) { if (!rec.IsDbRecord()) { // Updates with *Namespace types have fake lsn. Those updates should not be count in latestLsn - latestLsn = rec.extLsn; - } else if (rec.type == UpdateRecord::Type::AddNamespace) { - if (latestLsn.NsVersion().isEmpty() || latestLsn.NsVersion().Counter() < rec.extLsn.NsVersion().Counter()) { - latestLsn = ExtendedLsn(rec.extLsn.NsVersion(), lsn_t()); + latestLsn = rec.ExtLSN(); + } else if (rec.Type() == updates::URType::AddNamespace) { + if (latestLsn.NsVersion().isEmpty() || latestLsn.NsVersion().Counter() < rec.ExtLSN().NsVersion().Counter()) { + latestLsn = ExtendedLsn(rec.ExtLSN().NsVersion(), lsn_t()); } - } else if (rec.type == UpdateRecord::Type::DropNamespace) { + } else if (rec.Type() == updates::URType::DropNamespace) { latestLsn = ExtendedLsn(); } } @@ -105,13 +106,15 @@ class ReplThread { : serverId(_serverId), uid(_uid), client(config) {} void Reconnect(net::ev::dynamic_loop &loop, const ReplThreadConfig &config) { if (connObserverId.has_value()) { - client.RemoveConnectionStateObserver(*connObserverId); + auto err = client.RemoveConnectionStateObserver(*connObserverId); + (void)err; // ignored connObserverId.reset(); } client.Stop(); client::ConnectOpts opts; opts.CreateDBIfMissing().WithExpectedClusterID(config.ClusterID); - client.Connect(dsn, loop, opts); + auto err = client.Connect(dsn, loop, opts); + (void)err; // ignored; Error will be checked during the further requests } int serverId; @@ -119,7 +122,7 @@ class ReplThread { std::string dsn; client::CoroReindexer client; std::unique_ptr updateNotifier = std::make_unique(); - std::unordered_map + std::unordered_map namespaceData; // This map should not invalidate references uint64_t nextUpdateId = 0; bool requireResync = false; @@ -141,7 +144,8 @@ class ReplThread { swg, [&node]() noexcept { if (node.connObserverId.has_value()) { - node.client.RemoveConnectionStateObserver(*node.connObserverId); + auto err = node.client.RemoveConnectionStateObserver(*node.connObserverId); + (void)err; // ignore node.connObserverId.reset(); } node.client.Stop(); @@ -170,15 +174,15 @@ class ReplThread { void updatesNotifier() noexcept; void terminateNotifier() noexcept; std::tuple handleNetworkCheckRecord(Node &node, UpdatesQueueT::UpdatePtr &updPtr, uint16_t offset, - bool currentlyOnline, const UpdateRecord &rec) noexcept; + bool currentlyOnline, const updates::UpdateRecord &rec) noexcept; - Error syncNamespace(Node &node, const std::string &nsName, const ReplicationStateV2 &followerState); + Error syncNamespace(Node &, const NamespaceName &, const ReplicationStateV2 &followerState); [[nodiscard]] Error syncShardingConfig(Node &node) noexcept; UpdateApplyStatus nodeUpdatesHandlingLoop(Node &node) noexcept; bool handleUpdatesWithError(Node &node, const Error &err); Error checkIfReplicationAllowed(Node &node, LogLevel &logLevel); - UpdateApplyStatus applyUpdate(const UpdateRecord &rec, Node &node, NamespaceData &nsData) noexcept; + UpdateApplyStatus applyUpdate(const updates::UpdateRecord &rec, Node &node, NamespaceData &nsData) noexcept; static bool isNetworkError(const Error &err) noexcept { return err.code() == errNetwork; } static bool isTimeoutError(const Error &err) noexcept { return err.code() == errTimeout || err.code() == errCanceled; } static bool isLeaderChangedError(const Error &err) noexcept { return err.code() == errWrongReplicationData; } diff --git a/cpp_src/cluster/replication/roleswitcher.cc b/cpp_src/cluster/replication/roleswitcher.cc index 458faad51..f0fcdb0e2 100644 --- a/cpp_src/cluster/replication/roleswitcher.cc +++ b/cpp_src/cluster/replication/roleswitcher.cc @@ -138,7 +138,9 @@ void RoleSwitcher::switchNamespaces(const RaftInfo& newState, const ContainerT& status.leaderId = newState.leaderId; logInfo("%d: Setting new role '%s' and leader id %d for '%s'", cfg_.serverId, RaftInfo::RoleToStr(newState.role), status.leaderId, nsName); - nsPtr->SetClusterizationStatus(std::move(status), ctx_); + if (auto err = nsPtr->SetClusterizationStatus(std::move(status), ctx_); !err.ok()) { + logWarn("SetClusterizationStatus for the '%s' namespace error: %s", nsName, err.what()); + } } } } @@ -267,7 +269,7 @@ void RoleSwitcher::initialLeadersSync() { statsCollector_.OnInitialSyncDone(std::chrono::duration_cast(steady_clock_w::now() - roleSwitchTm_)); } -Error RoleSwitcher::awaitRoleSwitchForNamespace(client::CoroReindexer& client, std::string_view nsName, ReplicationStateV2& st) { +Error RoleSwitcher::awaitRoleSwitchForNamespace(client::CoroReindexer& client, const NamespaceName& nsName, ReplicationStateV2& st) { uint32_t step = 0; const bool isDbStatus = nsName.empty(); do { @@ -280,10 +282,10 @@ Error RoleSwitcher::awaitRoleSwitchForNamespace(client::CoroReindexer& client, s } if (step++ >= kMaxRetriesOnRoleSwitchAwait) { return Error(errTimeout, "Gave up on the remote node role switch awaiting for the '%s'", - isDbStatus ? "whole database" : nsName); + isDbStatus ? "whole database" : std::string_view(nsName)); } logInfo("%d: Awaiting role switch on the remote node for the '%s'. Current status is { role: %s; leader: %d }", cfg_.serverId, - isDbStatus ? "whole database" : nsName, st.clusterStatus.RoleStr(), st.clusterStatus.leaderId); + isDbStatus ? "whole database" : std::string_view(nsName), st.clusterStatus.RoleStr(), st.clusterStatus.leaderId); loop_.sleep(kRoleSwitchStepTime); auto [cur, next] = sharedSyncState_.GetRolesPair(); if (cur != next) { @@ -296,19 +298,19 @@ Error RoleSwitcher::awaitRoleSwitchForNamespace(client::CoroReindexer& client, s } while (true); } -Error RoleSwitcher::getNodesListForNs(std::string_view nsName, std::list& syncQueue) { +Error RoleSwitcher::getNodesListForNs(const NamespaceName& nsName, std::list& syncQueue) { // 1) Find most recent data among all the followers LeaderSyncQueue::Entry nsEntry; nsEntry.nsName = nsName; { ReplicationStateV2 state; - auto err = thisNode_.GetReplState(nsName, state, RdxContext()); + auto err = thisNode_.GetReplState(nsEntry.nsName, state, RdxContext()); if (err.ok()) { nsEntry.localLsn = nsEntry.latestLsn = ExtendedLsn(state.nsVersion, state.lastLsn); nsEntry.localData.hash = state.dataHash; nsEntry.localData.count = state.dataCount; } - logInfo("%d: Begin leader's sync for '%s'. Ns version: %d, lsn: %d", cfg_.serverId, nsName, nsEntry.localLsn.NsVersion(), + logInfo("%d: Begin leader's sync for '%s'. Ns version: %d, lsn: %d", cfg_.serverId, nsEntry.nsName, nsEntry.localLsn.NsVersion(), nsEntry.localLsn.LSN()); } size_t responses = 1; @@ -318,22 +320,22 @@ Error RoleSwitcher::getNodesListForNs(std::string_view nsName, std::list -Error RoleSwitcher::appendNsNamesFrom(RxT& rx, RoleSwitcher::NsNamesHashSetT& set) { +Error RoleSwitcher::appendNsNamesFrom(RxT& rx, NsNamesHashSetT& set) { std::vector nsDefs; Error err; if constexpr (std::is_same_v) { @@ -432,7 +434,8 @@ void RoleSwitcher::connectNodes() { client::ConnectOpts opts; opts.CreateDBIfMissing().WithExpectedClusterID(cfg_.clusterId); for (auto& node : nodes_) { - node.client.Connect(node.dsn, loop_, opts); + auto err = node.client.Connect(node.dsn, loop_, opts); + (void)err; // ignore. Error will be handled during the further requests } } diff --git a/cpp_src/cluster/replication/roleswitcher.h b/cpp_src/cluster/replication/roleswitcher.h index c2144fb07..1184a7c6d 100644 --- a/cpp_src/cluster/replication/roleswitcher.h +++ b/cpp_src/cluster/replication/roleswitcher.h @@ -18,7 +18,6 @@ class Logger; class RoleSwitcher { public: - using NsNamesHashSetT = fast_hash_set; struct Config { bool enableCompression = false; int clusterId = 0; @@ -69,11 +68,11 @@ class RoleSwitcher { void switchNamespaces(const RaftInfo &state, const ContainerT &namespaces); void handleInitialSync(RaftInfo::Role newRole); void initialLeadersSync(); - Error awaitRoleSwitchForNamespace(client::CoroReindexer &client, std::string_view nsName, ReplicationStateV2 &st); - Error getNodesListForNs(std::string_view nsName, std::list &syncQueue); + Error awaitRoleSwitchForNamespace(client::CoroReindexer &client, const NamespaceName& nsName, ReplicationStateV2 &st); + Error getNodesListForNs(const NamespaceName &nsName, std::list &syncQueue); NsNamesHashSetT collectNsNames(); template - Error appendNsNamesFrom(RxT &rx, RoleSwitcher::NsNamesHashSetT &set); + Error appendNsNamesFrom(RxT &rx, NsNamesHashSetT &set); void connectNodes(); void disconnectNodes(); size_t getConsensusCnt() const noexcept { return GetConsensusForN(nodes_.size() + 1); } diff --git a/cpp_src/cluster/replication/sharedsyncstate.h b/cpp_src/cluster/replication/sharedsyncstate.h index 140ea30b8..8f4f2a389 100644 --- a/cpp_src/cluster/replication/sharedsyncstate.h +++ b/cpp_src/cluster/replication/sharedsyncstate.h @@ -1,12 +1,10 @@ #pragma once -#include #include "cluster/config.h" +#include "core/namespace/namespacename.h" #include "estl/contexted_cond_var.h" #include "estl/fast_hash_set.h" -#include "estl/mutex.h" #include "estl/shared_mutex.h" -#include "tools/stringstools.h" namespace reindexer { namespace cluster { @@ -17,10 +15,11 @@ template class SharedSyncState { public: using GetNameF = std::function; - using ContainerT = fast_hash_set; + using ContainerT = NsNamesHashSetT; - void MarkSynchronized(std::string name) { + void MarkSynchronized(NamespaceName name) { std::unique_lock lck(mtx_); + assertrx_dbg(!name.empty()); if (current_.role == RaftInfo::Role::Leader) { auto res = synchronized_.emplace(std::move(name)); lck.unlock(); @@ -49,11 +48,10 @@ class SharedSyncState { assert(ReplThreadsCnt_); } template - void AwaitInitialSync(std::string_view name, const ContextT& ctx) const { - nocase_hash_str h; - std::size_t hash = h(name); + void AwaitInitialSync(const NamespaceName& name, const ContextT& ctx) const { shared_lock lck(mtx_); - while (!isInitialSyncDone(name, hash)) { + assertrx_dbg(!name.empty()); + while (!isInitialSyncDone(name)) { if (terminated_) { throw Error(errTerminated, "Cluster was terminated"); } @@ -61,10 +59,7 @@ class SharedSyncState { throw Error(errWrongReplicationData, "Node role was changed to follower"); } cond_.wait( - lck, - [this, hash, name]() noexcept { - return isInitialSyncDone(name, hash) || terminated_ || next_.role == RaftInfo::Role::Follower; - }, + lck, [this, &name]() noexcept { return isInitialSyncDone(name) || terminated_ || next_.role == RaftInfo::Role::Follower; }, ctx); } } @@ -82,11 +77,9 @@ class SharedSyncState { lck, [this]() noexcept { return isInitialSyncDone() || terminated_ || next_.role == RaftInfo::Role::Follower; }, ctx); } } - bool IsInitialSyncDone(std::string_view name) const { - nocase_hash_str h; - std::size_t hash = h(name); + bool IsInitialSyncDone(const NamespaceName& name) const { shared_lock lck(mtx_); - return isInitialSyncDone(name, hash); + return isInitialSyncDone(name); } bool IsInitialSyncDone() const { shared_lock lck(mtx_); @@ -145,14 +138,16 @@ class SharedSyncState { } private: - bool isInitialSyncDone(std::string_view name, std::size_t hash) const { - return !isRequireSync(name, hash) || (current_.role == RaftInfo::Role::Leader && synchronized_.count(name, hash)); + bool isInitialSyncDone(const NamespaceName& name) const { + assertrx_dbg(!name.empty()); + return !isRequireSync(name) || (current_.role == RaftInfo::Role::Leader && synchronized_.count(name)); } bool isInitialSyncDone() const noexcept { return !enabled_ || (next_.role == RaftInfo::Role::Leader && initialSyncDoneCnt_ == ReplThreadsCnt_); } - bool isRequireSync(std::string_view name, size_t hash) const noexcept { - return enabled_ && (requireSynchronization_.empty() || requireSynchronization_.count(name, hash)); + bool isRequireSync(const NamespaceName& name) const noexcept { + assertrx_dbg(!name.empty()); + return enabled_ && (requireSynchronization_.empty() || requireSynchronization_.count(name)); } bool isRunning() const noexcept { return enabled_ && !terminated_; } diff --git a/cpp_src/cluster/replication/updatesqueuepair.h b/cpp_src/cluster/replication/updatesqueuepair.h index 2253a0673..d62c215df 100644 --- a/cpp_src/cluster/replication/updatesqueuepair.h +++ b/cpp_src/cluster/replication/updatesqueuepair.h @@ -1,6 +1,9 @@ #pragma once -#include "updatesqueue.h" +#include "cluster/logger.h" +#include "cluster/stats/relicationstatscollector.h" +#include "core/namespace/namespacename.h" +#include "updates/updatesqueue.h" namespace reindexer { namespace cluster { @@ -10,8 +13,9 @@ class UpdatesQueuePair { public: using HashT = nocase_hash_str; using CompareT = nocase_equal_str; - using QueueT = UpdatesQueue; + using QueueT = updates::UpdatesQueue; using UpdatesContainerT = h_vector; + constexpr static uint64_t kMaxReplicas = QueueT::kMaxReplicas; struct Pair { std::shared_ptr sync; @@ -21,9 +25,8 @@ class UpdatesQueuePair { UpdatesQueuePair(uint64_t maxDataSize) : syncQueue_(std::make_shared(maxDataSize)), asyncQueue_(std::make_shared(maxDataSize)) {} - Pair GetQueue(const std::string &token) const { - const HashT h; - const size_t hash = h(token); + Pair GetQueue(const NamespaceName &token) const { + const size_t hash = token.hash(); Pair result; shared_lock lck(mtx_); if (syncQueue_->TokenIsInWhiteList(token, hash)) { @@ -47,18 +50,18 @@ class UpdatesQueuePair { std::lock_guard lck(mtx_); const auto maxDataSize = syncQueue_->MaxDataSize; syncQueue_ = std::make_shared(maxDataSize, statsCollector); - syncQueue_->Init(std::move(allowList), l); + syncQueue_->Init(std::move(allowList), &l); } template void ReinitAsyncQueue(ReplicationStatsCollector statsCollector, std::optional &&allowList, const Logger &l) { std::lock_guard lck(mtx_); const auto maxDataSize = asyncQueue_->MaxDataSize; asyncQueue_ = std::make_shared(maxDataSize, statsCollector); - asyncQueue_->Init(std::move(allowList), l); + asyncQueue_->Init(std::move(allowList), &l); } template std::pair Push(UpdatesContainerT &&data, std::function beforeWait, const ContextT &ctx) { - const auto shardPair = GetQueue(data[0].GetNsName()); + const auto shardPair = GetQueue(data[0].NsName()); if (shardPair.sync) { if (shardPair.async) { shardPair.async->template PushAsync(copyUpdatesContainer(data)); @@ -70,7 +73,7 @@ class UpdatesQueuePair { return std::make_pair(Error(), false); } std::pair PushNowait(UpdatesContainerT &&data) { - const auto shardPair = GetQueue(data[0].GetNsName()); + const auto shardPair = GetQueue(data[0].NsName()); if (shardPair.sync) { if (shardPair.async) { shardPair.async->template PushAsync(copyUpdatesContainer(data)); @@ -84,7 +87,7 @@ class UpdatesQueuePair { std::pair PushAsync(UpdatesContainerT &&data) { std::shared_ptr shard; { - std::string_view token(data[0].GetNsName()); + std::string_view token(data[0].NsName()); const HashT h; const size_t hash = h(token); shared_lock lck(mtx_); @@ -101,8 +104,8 @@ class UpdatesQueuePair { UpdatesContainerT copy; copy.reserve(data.size()); for (auto &d : data) { - copy.emplace_back(d.Clone()); - copy.back().emmiterServerId = -1; // async replication should not see it + // async replication should not see emmiter + copy.emplace_back(d.template Clone()); } return copy; } diff --git a/cpp_src/cluster/sharding/locatorserviceadapter.cc b/cpp_src/cluster/sharding/locatorserviceadapter.cc index bdd0bf95d..7a81179f4 100644 --- a/cpp_src/cluster/sharding/locatorserviceadapter.cc +++ b/cpp_src/cluster/sharding/locatorserviceadapter.cc @@ -10,8 +10,12 @@ int LocatorServiceAdapter::ActualShardId() const noexcept { return locator_->Act int64_t LocatorServiceAdapter::SourceId() const noexcept { return locator_->SourceId(); } -int LocatorServiceAdapter::GetShardId(std::string_view ns, const Item &item) const { return locator_->GetShardId(ns, item); } +std::pair LocatorServiceAdapter::GetShardIdKeyPair(std::string_view ns, const Item &item) const { + return locator_->GetShardIdKeyPair(ns, item); +} -ShardIDsContainer LocatorServiceAdapter::GetShardId(const Query &q) const { return locator_->GetShardId(q); } +std::pair LocatorServiceAdapter::GetShardIdKeyPair(const Query &q) const { + return locator_->GetShardIdKeyPair(q); +} } // namespace reindexer::sharding diff --git a/cpp_src/cluster/sharding/locatorserviceadapter.h b/cpp_src/cluster/sharding/locatorserviceadapter.h index 887626e5c..415b0d1e2 100644 --- a/cpp_src/cluster/sharding/locatorserviceadapter.h +++ b/cpp_src/cluster/sharding/locatorserviceadapter.h @@ -28,8 +28,8 @@ class LocatorServiceAdapter { std::shared_ptr GetShardConnection(std::string_view ns, int shardId, Error &status); int ActualShardId() const noexcept; int64_t SourceId() const noexcept; - int GetShardId(std::string_view ns, const Item &item) const; - ShardIDsContainer GetShardId(const Query &q) const; + std::pair GetShardIdKeyPair(std::string_view ns, const Item &item) const; + std::pair GetShardIdKeyPair(const Query &q) const; inline operator bool() const noexcept { return locator_.operator bool(); } inline void reset() noexcept { locator_.reset(); } diff --git a/cpp_src/cluster/sharding/sharding.cc b/cpp_src/cluster/sharding/sharding.cc index c83b4a233..809d7c0bf 100644 --- a/cpp_src/cluster/sharding/sharding.cc +++ b/cpp_src/cluster/sharding/sharding.cc @@ -14,7 +14,7 @@ constexpr size_t kMaxShardingProxyConnConcurrency = 1024; RoutingStrategy::RoutingStrategy(const cluster::ShardingConfig &config) : keys_(config) {} -bool RoutingStrategy::getHostIdForQuery(const Query &q, int &hostId) const { +bool RoutingStrategy::getHostIdForQuery(const Query &q, int &hostId, Variant &shardKey) const { bool containsKey = false; std::string_view ns = q.NsName(); for (auto it = q.Entries().cbegin(), next = it, end = q.Entries().cend(); it != end; ++it) { @@ -36,6 +36,8 @@ bool RoutingStrategy::getHostIdForQuery(const Query &q, int &hostId) const { } if (hostId == ShardingKeyType::ProxyOff) { hostId = id; + shardKey = qe.Values()[0]; + shardKey.EnsureHold(); } else if (hostId != id) { throw Error(errLogic, "Shard key from other node"); } @@ -88,16 +90,17 @@ bool RoutingStrategy::getHostIdForQuery(const Query &q, int &hostId) const { return containsKey; } -ShardIDsContainer RoutingStrategy::GetHostsIds(const Query &q) const { +std::pair RoutingStrategy::GetHostsIdsKeyPair(const Query &q) const { int hostId = ShardingKeyType::ProxyOff; + Variant shardKey; const std::string_view mainNs = q.NsName(); const bool mainNsIsSharded = keys_.IsSharded(mainNs); - bool hasShardingKeys = mainNsIsSharded && getHostIdForQuery(q, hostId); + bool hasShardingKeys = mainNsIsSharded && getHostIdForQuery(q, hostId, shardKey); const bool mainQueryToAllShards = mainNsIsSharded && !hasShardingKeys; for (const auto &jq : q.GetJoinQueries()) { if (!keys_.IsSharded(jq.NsName())) continue; - if (getHostIdForQuery(jq, hostId)) { + if (getHostIdForQuery(jq, hostId, shardKey)) { hasShardingKeys = true; } else { if (hasShardingKeys) { @@ -107,7 +110,7 @@ ShardIDsContainer RoutingStrategy::GetHostsIds(const Query &q) const { } for (const auto &mq : q.GetMergeQueries()) { if (!keys_.IsSharded(mq.NsName())) continue; - if (getHostIdForQuery(mq, hostId)) { + if (getHostIdForQuery(mq, hostId, shardKey)) { hasShardingKeys = true; } else { if (hasShardingKeys) { @@ -117,7 +120,7 @@ ShardIDsContainer RoutingStrategy::GetHostsIds(const Query &q) const { } for (const auto &sq : q.GetSubQueries()) { if (!keys_.IsSharded(sq.NsName())) continue; - if (getHostIdForQuery(sq, hostId)) { + if (getHostIdForQuery(sq, hostId, shardKey)) { hasShardingKeys = true; } else { if (hasShardingKeys) { @@ -134,12 +137,12 @@ ShardIDsContainer RoutingStrategy::GetHostsIds(const Query &q) const { throw Error(errLogic, "Query to all shard can't contain aggregations AVG, Facet or Distinct"); } } - return keys_.GetShardsIds(mainNs); + return {{keys_.GetShardsIds(mainNs)}, shardKey}; } - return {hostId}; + return {{hostId}, shardKey}; } -int RoutingStrategy::GetHostId(std::string_view ns, const Item &item) const { +std::pair RoutingStrategy::GetHostIdKeyPair(std::string_view ns, const Item &item) const { const auto nsIndex = keys_.GetIndex(ns); const VariantArray v = item[nsIndex.name]; if (!v.IsNullValue() && !v.empty()) { @@ -148,7 +151,7 @@ int RoutingStrategy::GetHostId(std::string_view ns, const Item &item) const { } assert(nsIndex.values); - return keys_.GetShardId(ns, v[0]); + return {keys_.GetShardId(ns, v[0]), Variant(v[0]).EnsureHold()}; } throw Error(errLogic, "Item does not contain proper sharding key for '%s' (key with name '%s' is expected)", ns, nsIndex.name); } @@ -213,7 +216,8 @@ std::shared_ptr ConnectStrategy::doReconnect(int shardID, Err const size_t size = connections_.size(); for (size_t i = 0; i < size; ++i) { - connections_[i]->Connect(dsns[i]); + auto err = connections_[i]->Connect(dsns[i]); + (void)err; // ignore; Errors will be checked during the further requests } std::shared_ptr stData = std::make_shared(size); @@ -253,9 +257,11 @@ std::shared_ptr ConnectStrategy::doReconnect(int shardID, Err conn.Stop(); logPrintf(LogTrace, "[sharding proxy] Shard %d reconnects to shard %d via %s", thisShard_, shardID, dsns[idx]); - conn.Connect(dsns[idx]); + err = conn.Connect(dsns[idx]); qr = client::QueryResults(); - err = conn.WithTimeout(config_.reconnectTimeout).Select(q, qr); + if (err.ok()) { + err = conn.WithTimeout(config_.reconnectTimeout).Select(q, qr); + } } reconnectStatus = std::move(err); if (!reconnectStatus.ok()) return {}; @@ -267,7 +273,7 @@ std::shared_ptr ConnectStrategy::doReconnect(int shardID, Err if (!reconnectStatus.ok()) return {}; cluster::ReplicationStats stats; - reconnectStatus = stats.FromJSON(wser.Slice()); + reconnectStatus = stats.FromJSON(giftStr(wser.Slice())); if (!reconnectStatus.ok()) return {}; if (stats.nodeStats.size()) { @@ -345,9 +351,7 @@ Error LocatorService::convertShardingKeysValues(KeyValueType fieldType, std::vec } return Error(); }, - [](OneOf) { - return Error{errLogic, "Sharding by composite index is unsupported"}; - }, + [](OneOf) { return Error{errLogic, "Sharding by composite index is unsupported"}; }, [fieldType](OneOf) { return Error{errLogic, "Unsupported field type: %s", fieldType.Name()}; }); @@ -469,7 +473,7 @@ ConnectionsPtr LocatorService::GetAllShardsConnections(Error &status) { } ConnectionsPtr LocatorService::GetShardsConnectionsWithId(const Query &q, Error &status) { - ShardIDsContainer ids = routingStrategy_.GetHostsIds(q); + ShardIDsContainer ids = routingStrategy_.GetHostsIdsKeyPair(q).first; assert(ids.size() > 0); if (ids.size() > 1) { return GetShardsConnections(q.NsName(), ShardingKeyType::NotSetShard, status); diff --git a/cpp_src/cluster/sharding/sharding.h b/cpp_src/cluster/sharding/sharding.h index a36ed62a6..a96217cfd 100644 --- a/cpp_src/cluster/sharding/sharding.h +++ b/cpp_src/cluster/sharding/sharding.h @@ -26,8 +26,8 @@ class RoutingStrategy { explicit RoutingStrategy(const cluster::ShardingConfig &config); ~RoutingStrategy() = default; - ShardIDsContainer GetHostsIds(const Query &) const; - int GetHostId(std::string_view ns, const Item &) const; + std::pair GetHostsIdsKeyPair(const Query &) const; + std::pair GetHostIdKeyPair(std::string_view ns, const Item &) const; ShardIDsContainer GetHostsIds(std::string_view ns) const { return keys_.GetShardsIds(ns); } ShardIDsContainer GetHostsIds() const { return keys_.GetShardsIds(); } bool IsShardingKey(std::string_view ns, std::string_view index) const { return keys_.IsShardIndex(ns, index); } @@ -35,7 +35,7 @@ class RoutingStrategy { int GetDefaultHost(std::string_view ns) const { return keys_.GetDefaultHost(ns); } private: - bool getHostIdForQuery(const Query &, int ¤tId) const; + bool getHostIdForQuery(const Query &, int ¤tId, Variant &shardKey) const; ShardingKeys keys_; }; @@ -129,8 +129,10 @@ class LocatorService { Error Start(); Error AwaitShards(const RdxContext &ctx) { return networkMonitor_.AwaitShards(ctx); } bool IsSharded(std::string_view ns) const noexcept { return routingStrategy_.IsSharded(ns); } - int GetShardId(std::string_view ns, const Item &item) const { return routingStrategy_.GetHostId(ns, item); } - ShardIDsContainer GetShardId(const Query &q) const { return routingStrategy_.GetHostsIds(q); } + std::pair GetShardIdKeyPair(std::string_view ns, const Item &item) const { + return routingStrategy_.GetHostIdKeyPair(ns, item); + } + std::pair GetShardIdKeyPair(const Query &q) const { return routingStrategy_.GetHostsIdsKeyPair(q); } std::shared_ptr GetShardConnection(std::string_view ns, int shardId, Error &status); ConnectionsPtr GetShardsConnections(std::string_view ns, int shardId, Error &status); @@ -138,7 +140,7 @@ class LocatorService { ConnectionsPtr GetShardsConnections(Error &status) { return GetShardsConnections("", -1, status); } ConnectionsPtr GetAllShardsConnections(Error &status); ShardConnection GetShardConnectionWithId(std::string_view ns, const Item &item, Error &status) { - int shardId = routingStrategy_.GetHostId(ns, item); + int shardId = routingStrategy_.GetHostIdKeyPair(ns, item).first; return ShardConnection(GetShardConnection(ns, shardId, status), shardId); } diff --git a/cpp_src/cluster/stats/relicationstatscollector.h b/cpp_src/cluster/stats/relicationstatscollector.h index 6e8c8eaeb..c14be6375 100644 --- a/cpp_src/cluster/stats/relicationstatscollector.h +++ b/cpp_src/cluster/stats/relicationstatscollector.h @@ -35,21 +35,51 @@ class ReplicationStatsCollector { if (counter_) counter_->Init(thisNode, nodes, namespaces); } - void OnWalSync(std::chrono::microseconds time) noexcept { counter_->OnWalSync(time); } - void OnForceSync(std::chrono::microseconds time) noexcept { counter_->OnForceSync(time); } - void OnInitialWalSync(std::chrono::microseconds time) noexcept { counter_->OnInitialWalSync(time); } - void OnInitialForceSync(std::chrono::microseconds time) noexcept { counter_->OnInitialForceSync(time); } - void OnInitialSyncDone(std::chrono::microseconds time) noexcept { counter_->OnInitialSyncDone(time); } - void OnUpdatePushed(int64_t updateId, size_t size) noexcept { counter_->OnUpdatePushed(updateId, size); } - void OnUpdateApplied(size_t nodeId, int64_t updateId) noexcept { counter_->OnUpdateApplied(nodeId, updateId); } - void OnUpdatesDrop(int64_t updateId, size_t size) noexcept { counter_->OnUpdatesDrop(updateId, size); } - void OnUpdateReplicated(int64_t updateId) noexcept { counter_->OnUpdateReplicated(updateId); } - void OnUpdateErased(int64_t updateId, size_t size) noexcept { counter_->OnUpdateErased(updateId, size); } - void OnStatusChanged(size_t nodeId, NodeStats::Status status) { counter_->OnStatusChanged(nodeId, status); } - void OnSyncStateChanged(size_t nodeId, NodeStats::SyncState state) { counter_->OnSyncStateChanged(nodeId, state); } - void OnServerIdChanged(size_t nodeId, int serverId) noexcept { counter_->OnServerIdChanged(nodeId, serverId); } - void SaveNodeError(size_t nodeId, const Error& err) { counter_->SaveNodeError(nodeId, err); } - void Reset() { counter_->Reset(); } + void OnWalSync(std::chrono::microseconds time) noexcept { + if (counter_) counter_->OnWalSync(time); + } + void OnForceSync(std::chrono::microseconds time) noexcept { + if (counter_) counter_->OnForceSync(time); + } + void OnInitialWalSync(std::chrono::microseconds time) noexcept { + if (counter_) counter_->OnInitialWalSync(time); + } + void OnInitialForceSync(std::chrono::microseconds time) noexcept { + if (counter_) counter_->OnInitialForceSync(time); + } + void OnInitialSyncDone(std::chrono::microseconds time) noexcept { + if (counter_) counter_->OnInitialSyncDone(time); + } + void OnUpdatePushed(int64_t updateId, size_t size) noexcept { + if (counter_) counter_->OnUpdatePushed(updateId, size); + } + void OnUpdateApplied(size_t nodeId, int64_t updateId) noexcept { + if (counter_) counter_->OnUpdateApplied(nodeId, updateId); + } + void OnUpdatesDrop(int64_t updateId, size_t size) noexcept { + if (counter_) counter_->OnUpdatesDrop(updateId, size); + } + void OnUpdateHandled(int64_t updateId) noexcept { + if (counter_) counter_->OnUpdateHandled(updateId); + } + void OnUpdateErased(int64_t updateId, size_t size) noexcept { + if (counter_) counter_->OnUpdateErased(updateId, size); + } + void OnStatusChanged(size_t nodeId, NodeStats::Status status) { + if (counter_) counter_->OnStatusChanged(nodeId, status); + } + void OnSyncStateChanged(size_t nodeId, NodeStats::SyncState state) { + if (counter_) counter_->OnSyncStateChanged(nodeId, state); + } + void OnServerIdChanged(size_t nodeId, int serverId) noexcept { + if (counter_) counter_->OnServerIdChanged(nodeId, serverId); + } + void SaveNodeError(size_t nodeId, const Error& err) { + if (counter_) counter_->SaveNodeError(nodeId, err); + } + void Reset() { + if (counter_) counter_->Reset(); + } ReplicationStats Get() const { if (counter_) { return counter_->Get(); diff --git a/cpp_src/cluster/stats/replicationstats.h b/cpp_src/cluster/stats/replicationstats.h index 8076d2083..140023cc6 100644 --- a/cpp_src/cluster/stats/replicationstats.h +++ b/cpp_src/cluster/stats/replicationstats.h @@ -190,7 +190,7 @@ class ReplicationStatCounter { lastErasedUpdateId_.store(updateId, std::memory_order_relaxed); allocatedUpdatesSizeBytes_.fetch_sub(size, std::memory_order_relaxed); } - void OnUpdateReplicated(int64_t updateId) noexcept { lastReplicatedUpdateId_.store(updateId, std::memory_order_relaxed); } + void OnUpdateHandled(int64_t updateId) noexcept { lastReplicatedUpdateId_.store(updateId, std::memory_order_relaxed); } void OnUpdateErased(int64_t updateId, size_t size) noexcept { lastErasedUpdateId_.store(updateId, std::memory_order_relaxed); allocatedUpdatesSizeBytes_.fetch_sub(size, std::memory_order_relaxed); @@ -257,6 +257,6 @@ class ReplicationStatCounter { std::optional thisNode_; mutable read_write_spinlock mtx_; }; -}; // namespace cluster +} // namespace cluster } // namespace reindexer diff --git a/cpp_src/cluster/updaterecord.cc b/cpp_src/cluster/updaterecord.cc deleted file mode 100644 index 437355e57..000000000 --- a/cpp_src/cluster/updaterecord.cc +++ /dev/null @@ -1,589 +0,0 @@ -#include "updaterecord.h" - -namespace reindexer { -namespace cluster { - -UpdateRecord::UpdateRecord(UpdateRecord::Type _type, uint32_t _nodeUid, bool online) : type(_type), emmiterServerId(-1) { - assert(_type == Type::NodeNetworkCheck); - data.emplace>(new NodeNetworkCheckRecord{_nodeUid, online}); -} - -UpdateRecord::UpdateRecord(UpdateRecord::Type _type, std::string _nsName, int _emmiterServerId) - : type(_type), nsName(std::move(_nsName)), emmiterServerId(_emmiterServerId) { - switch (type) { - case Type::EmptyUpdate: - break; - case Type::None: - case Type::ItemUpdate: - case Type::ItemUpsert: - case Type::ItemDelete: - case Type::ItemInsert: - case Type::ItemUpdateTx: - case Type::ItemUpsertTx: - case Type::ItemDeleteTx: - case Type::ItemInsertTx: - case Type::IndexAdd: - case Type::IndexDrop: - case Type::IndexUpdate: - case Type::PutMeta: - case Type::PutMetaTx: - case Type::DeleteMeta: - case Type::UpdateQuery: - case Type::DeleteQuery: - case Type::UpdateQueryTx: - case Type::DeleteQueryTx: - case Type::SetSchema: - case Type::Truncate: - case Type::BeginTx: - case Type::CommitTx: - case Type::AddNamespace: - case Type::DropNamespace: - case Type::CloseNamespace: - case Type::RenameNamespace: - case Type::ResyncNamespaceGeneric: - case Type::ResyncNamespaceLeaderInit: - case Type::ResyncOnUpdatesDrop: - case Type::NodeNetworkCheck: - case Type::SetTagsMatcher: - case Type::SetTagsMatcherTx: - case Type::SaveShardingConfig: - case Type::ApplyShardingConfig: - case Type::ResetOldShardingConfig: - case Type::ResetCandidateConfig: - case Type::RollbackCandidateConfig: - assert(false); - } -} - -UpdateRecord::UpdateRecord(Type _type, std::string _nsName, lsn_t _lsn, lsn_t _nsVersion, int _emmiterServerId) - : type(_type), nsName(std::move(_nsName)), extLsn(_nsVersion, _lsn), emmiterServerId(_emmiterServerId) { - switch (type) { - case Type::Truncate: - case Type::BeginTx: - case Type::CommitTx: - case Type::DropNamespace: - case Type::CloseNamespace: - case Type::ResyncNamespaceGeneric: - case Type::ResyncNamespaceLeaderInit: - break; - case Type::None: - case Type::ItemUpdate: - case Type::ItemUpsert: - case Type::ItemDelete: - case Type::ItemInsert: - case Type::ItemUpdateTx: - case Type::ItemUpsertTx: - case Type::ItemDeleteTx: - case Type::ItemInsertTx: - case Type::IndexAdd: - case Type::IndexDrop: - case Type::IndexUpdate: - case Type::PutMeta: - case Type::PutMetaTx: - case Type::DeleteMeta: - case Type::UpdateQuery: - case Type::DeleteQuery: - case Type::UpdateQueryTx: - case Type::DeleteQueryTx: - case Type::SetSchema: - case Type::AddNamespace: - case Type::RenameNamespace: - case Type::ResyncOnUpdatesDrop: - case Type::NodeNetworkCheck: - case Type::SetTagsMatcher: - case Type::SetTagsMatcherTx: - case Type::EmptyUpdate: - case Type::SaveShardingConfig: - case Type::ApplyShardingConfig: - case Type::ResetOldShardingConfig: - case Type::ResetCandidateConfig: - case Type::RollbackCandidateConfig: - assert(false); - } -} - -UpdateRecord::UpdateRecord(Type _type, std::string _nsName, lsn_t _lsn, lsn_t _nsVersion, int _emmiterServerId, std::string _data) - : type(_type), nsName(std::move(_nsName)), extLsn(_nsVersion, _lsn), emmiterServerId(_emmiterServerId) { - switch (type) { - case Type::RenameNamespace: - data.emplace>(new RenameNamespaceReplicationRecord{std::move(_data)}); - break; - case Type::SetSchema: - data.emplace>(new SchemaReplicationRecord{std::move(_data)}); - break; - case Type::UpdateQuery: - case Type::DeleteQuery: - case Type::UpdateQueryTx: - case Type::DeleteQueryTx: - data.emplace>(new QueryReplicationRecord{std::move(_data)}); - break; - case Type::None: - case Type::ItemUpdate: - case Type::ItemUpsert: - case Type::ItemDelete: - case Type::ItemInsert: - case Type::ItemUpdateTx: - case Type::ItemUpsertTx: - case Type::ItemDeleteTx: - case Type::ItemInsertTx: - case Type::IndexAdd: - case Type::IndexDrop: - case Type::IndexUpdate: - case Type::PutMeta: - case Type::PutMetaTx: - case Type::DeleteMeta: - case Type::Truncate: - case Type::BeginTx: - case Type::CommitTx: - case Type::AddNamespace: - case Type::DropNamespace: - case Type::CloseNamespace: - case Type::ResyncNamespaceGeneric: - case Type::ResyncNamespaceLeaderInit: - case Type::ResyncOnUpdatesDrop: - case Type::NodeNetworkCheck: - case Type::SetTagsMatcher: - case Type::SetTagsMatcherTx: - case Type::EmptyUpdate: - case Type::SaveShardingConfig: - case Type::ApplyShardingConfig: - case Type::ResetOldShardingConfig: - case Type::ResetCandidateConfig: - case Type::RollbackCandidateConfig: - assert(false); - } -} - -UpdateRecord::UpdateRecord(Type _type, std::string _nsName, lsn_t _lsn, lsn_t _nsVersion, int _emmiterServerId, WrSerializer&& _data) - : type(_type), nsName(std::move(_nsName)), extLsn(_nsVersion, _lsn), emmiterServerId(_emmiterServerId) { - switch (type) { - case Type::ItemUpdate: - case Type::ItemUpsert: - case Type::ItemDelete: - case Type::ItemInsert: - case Type::ItemUpdateTx: - case Type::ItemUpsertTx: - case Type::ItemDeleteTx: - case Type::ItemInsertTx: { - data.emplace>(new ItemReplicationRecord{std::move(_data)}); - break; - } - case Type::None: - case Type::IndexAdd: - case Type::IndexDrop: - case Type::IndexUpdate: - case Type::PutMeta: - case Type::PutMetaTx: - case Type::DeleteMeta: - case Type::UpdateQuery: - case Type::DeleteQuery: - case Type::UpdateQueryTx: - case Type::DeleteQueryTx: - case Type::SetSchema: - case Type::Truncate: - case Type::BeginTx: - case Type::CommitTx: - case Type::AddNamespace: - case Type::DropNamespace: - case Type::CloseNamespace: - case Type::RenameNamespace: - case Type::ResyncNamespaceGeneric: - case Type::ResyncNamespaceLeaderInit: - case Type::ResyncOnUpdatesDrop: - case Type::NodeNetworkCheck: - case Type::SetTagsMatcher: - case Type::SetTagsMatcherTx: - case Type::EmptyUpdate: - case Type::SaveShardingConfig: - case Type::ApplyShardingConfig: - case Type::ResetOldShardingConfig: - case Type::ResetCandidateConfig: - case Type::RollbackCandidateConfig: - assert(false); - } -} - -UpdateRecord::UpdateRecord(UpdateRecord::Type _type, std::string _nsName, lsn_t _lsn, lsn_t _nsVersion, int _emmiterServerId, - const TagsMatcher& _tm) - : type(_type), nsName(std::move(_nsName)), extLsn(_nsVersion, _lsn), emmiterServerId(_emmiterServerId) { - switch (type) { - case Type::SetTagsMatcher: - case Type::SetTagsMatcherTx: { - TagsMatcher tm; - WrSerializer wser; - _tm.serialize(wser); - Serializer ser(wser.Slice()); - tm.deserialize(ser, _tm.version(), _tm.stateToken()); - data.emplace>( - new TagsMatcherReplicationRecord{std::move(tm), wser.Slice().size() * 4}); - break; - } - case Type::None: - case Type::ItemUpdate: - case Type::ItemUpsert: - case Type::ItemDelete: - case Type::ItemInsert: - case Type::ItemUpdateTx: - case Type::ItemUpsertTx: - case Type::ItemDeleteTx: - case Type::ItemInsertTx: - case Type::IndexAdd: - case Type::IndexDrop: - case Type::IndexUpdate: - case Type::PutMeta: - case Type::PutMetaTx: - case Type::DeleteMeta: - case Type::UpdateQuery: - case Type::DeleteQuery: - case Type::UpdateQueryTx: - case Type::DeleteQueryTx: - case Type::SetSchema: - case Type::Truncate: - case Type::BeginTx: - case Type::CommitTx: - case Type::AddNamespace: - case Type::DropNamespace: - case Type::CloseNamespace: - case Type::RenameNamespace: - case Type::ResyncNamespaceGeneric: - case Type::ResyncNamespaceLeaderInit: - case Type::ResyncOnUpdatesDrop: - case Type::NodeNetworkCheck: - case Type::EmptyUpdate: - case Type::SaveShardingConfig: - case Type::ApplyShardingConfig: - case Type::ResetOldShardingConfig: - case Type::ResetCandidateConfig: - case Type::RollbackCandidateConfig: - assert(false); - } -} - -UpdateRecord::UpdateRecord(Type _type, std::string _nsName, lsn_t _lsn, lsn_t _nsVersion, int _emmiterServerId, IndexDef _idef) - : type(_type), - nsName(std::move(_nsName)), - extLsn(_nsVersion, _lsn), - data(std::unique_ptr(new IndexReplicationRecord{std::move(_idef)})), - emmiterServerId(_emmiterServerId) { - switch (type) { - case Type::IndexAdd: - case Type::IndexDrop: - case Type::IndexUpdate: - break; - case Type::None: - case Type::ItemUpdate: - case Type::ItemUpsert: - case Type::ItemDelete: - case Type::ItemInsert: - case Type::ItemUpdateTx: - case Type::ItemUpsertTx: - case Type::ItemDeleteTx: - case Type::ItemInsertTx: - case Type::PutMeta: - case Type::PutMetaTx: - case Type::DeleteMeta: - case Type::UpdateQuery: - case Type::DeleteQuery: - case Type::UpdateQueryTx: - case Type::DeleteQueryTx: - case Type::SetSchema: - case Type::Truncate: - case Type::BeginTx: - case Type::CommitTx: - case Type::AddNamespace: - case Type::DropNamespace: - case Type::CloseNamespace: - case Type::RenameNamespace: - case Type::ResyncNamespaceGeneric: - case Type::ResyncNamespaceLeaderInit: - case Type::ResyncOnUpdatesDrop: - case Type::NodeNetworkCheck: - case Type::SetTagsMatcher: - case Type::SetTagsMatcherTx: - case Type::EmptyUpdate: - case Type::SaveShardingConfig: - case Type::ApplyShardingConfig: - case Type::ResetOldShardingConfig: - case Type::ResetCandidateConfig: - case Type::RollbackCandidateConfig: - assert(false); - } -} - -UpdateRecord::UpdateRecord(Type _type, std::string _nsName, lsn_t _nsVersion, int _emmiterServerId, NamespaceDef _def, int64_t _stateToken) - : type(_type), nsName(std::move(_nsName)), extLsn(_nsVersion, lsn_t(0, 0)), emmiterServerId(_emmiterServerId) { - switch (type) { - case Type::AddNamespace: - data.emplace>(new AddNamespaceReplicationRecord{std::move(_def), _stateToken}); - break; - case Type::IndexAdd: - case Type::IndexDrop: - case Type::IndexUpdate: - case Type::None: - case Type::ItemUpdate: - case Type::ItemUpsert: - case Type::ItemDelete: - case Type::ItemInsert: - case Type::ItemUpdateTx: - case Type::ItemUpsertTx: - case Type::ItemDeleteTx: - case Type::ItemInsertTx: - case Type::PutMeta: - case Type::PutMetaTx: - case Type::DeleteMeta: - case Type::UpdateQuery: - case Type::DeleteQuery: - case Type::UpdateQueryTx: - case Type::DeleteQueryTx: - case Type::SetSchema: - case Type::Truncate: - case Type::BeginTx: - case Type::CommitTx: - case Type::DropNamespace: - case Type::CloseNamespace: - case Type::RenameNamespace: - case Type::ResyncNamespaceGeneric: - case Type::ResyncNamespaceLeaderInit: - case Type::ResyncOnUpdatesDrop: - case Type::NodeNetworkCheck: - case Type::SetTagsMatcher: - case Type::SetTagsMatcherTx: - case Type::EmptyUpdate: - case Type::SaveShardingConfig: - case Type::ApplyShardingConfig: - case Type::ResetOldShardingConfig: - case Type::ResetCandidateConfig: - case Type::RollbackCandidateConfig: - assert(false); - } -} - -UpdateRecord::UpdateRecord(UpdateRecord::Type _type, std::string _nsName, lsn_t _lsn, lsn_t _nsVersion, int _emmiterServerId, - std::string _k, std::string _v) - : type(_type), nsName(std::move(_nsName)), extLsn(_nsVersion, _lsn), emmiterServerId(_emmiterServerId) { - switch (type) { - case Type::PutMeta: - case Type::PutMetaTx: - case Type::DeleteMeta: - data.emplace>(new MetaReplicationRecord{std::move(_k), std::move(_v)}); - break; - case Type::IndexAdd: - case Type::IndexDrop: - case Type::IndexUpdate: - case Type::None: - case Type::ItemUpdate: - case Type::ItemUpsert: - case Type::ItemDelete: - case Type::ItemInsert: - case Type::ItemUpdateTx: - case Type::ItemUpsertTx: - case Type::ItemDeleteTx: - case Type::ItemInsertTx: - case Type::UpdateQuery: - case Type::DeleteQuery: - case Type::UpdateQueryTx: - case Type::DeleteQueryTx: - case Type::SetSchema: - case Type::Truncate: - case Type::BeginTx: - case Type::CommitTx: - case Type::AddNamespace: - case Type::DropNamespace: - case Type::CloseNamespace: - case Type::RenameNamespace: - case Type::ResyncNamespaceGeneric: - case Type::ResyncNamespaceLeaderInit: - case Type::ResyncOnUpdatesDrop: - case Type::NodeNetworkCheck: - case Type::SetTagsMatcher: - case Type::SetTagsMatcherTx: - case Type::EmptyUpdate: - case Type::SaveShardingConfig: - case Type::ApplyShardingConfig: - case Type::ResetOldShardingConfig: - case Type::ResetCandidateConfig: - case Type::RollbackCandidateConfig: - assert(false); - } -} - -UpdateRecord::UpdateRecord(Type _type, int _emmiterServerId, std::string _data, int64_t sourceId) - : type(_type), emmiterServerId(_emmiterServerId) { - switch (type) { - case Type::SaveShardingConfig: - data.emplace>(new SaveNewShardingCfgRecord{std::move(_data), sourceId}); - break; - case Type::PutMeta: - case Type::PutMetaTx: - case Type::DeleteMeta: - case Type::IndexAdd: - case Type::IndexDrop: - case Type::IndexUpdate: - case Type::None: - case Type::ItemUpdate: - case Type::ItemUpsert: - case Type::ItemDelete: - case Type::ItemInsert: - case Type::ItemUpdateTx: - case Type::ItemUpsertTx: - case Type::ItemDeleteTx: - case Type::ItemInsertTx: - case Type::UpdateQuery: - case Type::DeleteQuery: - case Type::UpdateQueryTx: - case Type::DeleteQueryTx: - case Type::SetSchema: - case Type::Truncate: - case Type::BeginTx: - case Type::CommitTx: - case Type::AddNamespace: - case Type::DropNamespace: - case Type::CloseNamespace: - case Type::RenameNamespace: - case Type::ResyncNamespaceGeneric: - case Type::ResyncNamespaceLeaderInit: - case Type::ResyncOnUpdatesDrop: - case Type::NodeNetworkCheck: - case Type::SetTagsMatcher: - case Type::SetTagsMatcherTx: - case Type::EmptyUpdate: - case Type::ApplyShardingConfig: - case Type::ResetOldShardingConfig: - case Type::ResetCandidateConfig: - case Type::RollbackCandidateConfig: - assert(false); - } -} - -UpdateRecord::UpdateRecord(Type _type, int _emmiterServerId, int64_t sourceId) : type(_type), emmiterServerId(_emmiterServerId) { - switch (type) { - case Type::ApplyShardingConfig: - data.emplace>(new ApplyNewShardingCfgRecord{sourceId}); - break; - case Type::ResetOldShardingConfig: - case Type::ResetCandidateConfig: - case Type::RollbackCandidateConfig: - data.emplace>(new ResetShardingCfgRecord{sourceId}); - break; - case Type::PutMeta: - case Type::PutMetaTx: - case Type::DeleteMeta: - case Type::IndexAdd: - case Type::IndexDrop: - case Type::IndexUpdate: - case Type::None: - case Type::ItemUpdate: - case Type::ItemUpsert: - case Type::ItemDelete: - case Type::ItemInsert: - case Type::ItemUpdateTx: - case Type::ItemUpsertTx: - case Type::ItemDeleteTx: - case Type::ItemInsertTx: - case Type::UpdateQuery: - case Type::DeleteQuery: - case Type::UpdateQueryTx: - case Type::DeleteQueryTx: - case Type::SetSchema: - case Type::Truncate: - case Type::BeginTx: - case Type::CommitTx: - case Type::AddNamespace: - case Type::DropNamespace: - case Type::CloseNamespace: - case Type::RenameNamespace: - case Type::ResyncNamespaceGeneric: - case Type::ResyncNamespaceLeaderInit: - case Type::ResyncOnUpdatesDrop: - case Type::NodeNetworkCheck: - case Type::SetTagsMatcher: - case Type::SetTagsMatcherTx: - case Type::EmptyUpdate: - case Type::SaveShardingConfig: - assert(false); - } -} - -size_t UpdateRecord::DataSize() const noexcept { - switch (type) { - case Type::ItemUpdate: - case Type::ItemUpsert: - case Type::ItemDelete: - case Type::ItemInsert: - case Type::ItemUpdateTx: - case Type::ItemUpsertTx: - case Type::ItemDeleteTx: - case Type::ItemInsertTx: - return std::get>(data)->Size(); - case Type::IndexAdd: - case Type::IndexDrop: - case Type::IndexUpdate: - return std::get>(data)->Size(); - case Type::PutMeta: - case Type::PutMetaTx: - case Type::DeleteMeta: - return std::get>(data)->Size(); - case Type::UpdateQuery: - case Type::DeleteQuery: - case Type::UpdateQueryTx: - case Type::DeleteQueryTx: - return std::get>(data)->Size(); - case Type::SetSchema: - return std::get>(data)->Size(); - case Type::Truncate: - case Type::BeginTx: - case Type::CommitTx: - case Type::DropNamespace: - case Type::CloseNamespace: - case Type::ResyncNamespaceGeneric: - case Type::ResyncNamespaceLeaderInit: - case Type::ResyncOnUpdatesDrop: - case Type::EmptyUpdate: - return 0; - case Type::AddNamespace: - return std::get>(data)->Size(); - case Type::RenameNamespace: - return std::get>(data)->Size(); - case Type::NodeNetworkCheck: - return std::get>(data)->Size(); - case Type::SetTagsMatcher: - case Type::SetTagsMatcherTx: - return std::get>(data)->Size(); - case Type::SaveShardingConfig: - return std::get>(data)->Size(); - case Type::ApplyShardingConfig: - return std::get>(data)->Size(); - case Type::ResetOldShardingConfig: - case Type::ResetCandidateConfig: - case Type::RollbackCandidateConfig: - return std::get>(data)->Size(); - case Type::None: - default: - std::abort(); - } -} - -struct DataCopier { - template - void operator()(const std::unique_ptr& v) const { - if (v) { - rec.data.emplace>(new T(*v)); - } - } - - UpdateRecord& rec; -}; - -UpdateRecord UpdateRecord::Clone() const { - UpdateRecord rec; - rec.type = type; - rec.nsName = nsName; - rec.extLsn = extLsn; - rec.emmiterServerId = emmiterServerId; - DataCopier visitor{rec}; - std::visit(visitor, data); - return rec; -} - -} // namespace cluster -} // namespace reindexer diff --git a/cpp_src/cluster/updaterecord.h b/cpp_src/cluster/updaterecord.h deleted file mode 100644 index ca4f2f958..000000000 --- a/cpp_src/cluster/updaterecord.h +++ /dev/null @@ -1,230 +0,0 @@ -#pragma once - -#include -#include -#include "client/cororeindexer.h" -#include "core/cjson/tagsmatcher.h" -#include "tools/lsn.h" - -namespace reindexer { -namespace cluster { - -struct ItemReplicationRecord { - size_t Size() const noexcept { return sizeof(ItemReplicationRecord) + (cjson.HasHeap() ? cjson.Slice().size() : 0); } - ItemReplicationRecord(WrSerializer&& _cjson) noexcept : cjson(std::move(_cjson)) {} - ItemReplicationRecord(ItemReplicationRecord&&) = default; - ItemReplicationRecord(const ItemReplicationRecord& o) { cjson.Write(o.cjson.Slice()); } - - WrSerializer cjson; -}; - -struct TagsMatcherReplicationRecord { - size_t Size() const noexcept { return sizeof(TagsMatcherReplicationRecord) + tmSize; } - - TagsMatcher tm; - size_t tmSize; -}; - -struct IndexReplicationRecord { - size_t Size() const noexcept { return sizeof(IndexReplicationRecord) + idef.HeapSize(); } - - IndexDef idef; -}; - -struct MetaReplicationRecord { - size_t Size() const noexcept { return sizeof(MetaReplicationRecord) + key.size() + value.size(); } - - std::string key; - std::string value; -}; - -struct QueryReplicationRecord { - size_t Size() const noexcept { return sizeof(QueryReplicationRecord) + sql.size(); } - - std::string sql; -}; - -struct SchemaReplicationRecord { - size_t Size() const noexcept { return sizeof(SchemaReplicationRecord) + schema.size(); } - - std::string schema; -}; - -struct AddNamespaceReplicationRecord { - size_t Size() const noexcept { return sizeof(AddNamespaceReplicationRecord) + def.HeapSize(); } - - NamespaceDef def; - int64_t stateToken; -}; - -struct RenameNamespaceReplicationRecord { - size_t Size() const noexcept { return sizeof(RenameNamespaceReplicationRecord) + dstNsName.size(); } - - std::string dstNsName; -}; - -struct NodeNetworkCheckRecord { - size_t Size() const noexcept { return sizeof(NodeNetworkCheckRecord); } - - uint32_t nodeUid; - bool online; -}; - -struct SaveNewShardingCfgRecord { - size_t Size() const noexcept { return sizeof(SaveNewShardingCfgRecord) + config.size(); } - - std::string config; - int64_t sourceId; -}; - -struct ApplyNewShardingCfgRecord { - size_t Size() const noexcept { return sizeof(ApplyNewShardingCfgRecord); } - - int64_t sourceId; -}; - -struct ResetShardingCfgRecord { - size_t Size() const noexcept { return sizeof(ResetShardingCfgRecord); } - - int64_t sourceId; -}; - -struct UpdateRecord { - enum class Type { - None = 0, - ItemUpdate = 1, - ItemUpsert = 2, - ItemDelete = 3, - ItemInsert = 4, - ItemUpdateTx = 5, - ItemUpsertTx = 6, - ItemDeleteTx = 7, - ItemInsertTx = 8, - IndexAdd = 9, - IndexDrop = 10, - IndexUpdate = 11, - PutMeta = 12, - PutMetaTx = 13, - UpdateQuery = 14, - DeleteQuery = 15, - UpdateQueryTx = 16, - DeleteQueryTx = 17, - SetSchema = 18, - Truncate = 19, - BeginTx = 20, - CommitTx = 21, - AddNamespace = 22, - DropNamespace = 23, - CloseNamespace = 24, - RenameNamespace = 25, - ResyncNamespaceGeneric = 26, - ResyncNamespaceLeaderInit = 27, - ResyncOnUpdatesDrop = 28, - EmptyUpdate = 29, - NodeNetworkCheck = 30, - SetTagsMatcher = 31, - SetTagsMatcherTx = 32, - SaveShardingConfig = 33, - ApplyShardingConfig = 34, - ResetOldShardingConfig = 35, - ResetCandidateConfig = 36, - RollbackCandidateConfig = 37, - DeleteMeta = 38, - }; - - UpdateRecord() = default; - UpdateRecord(Type _type, uint32_t _nodeUid, bool online); - UpdateRecord(Type _type, std::string _nsName, int _emmiterServerId); - UpdateRecord(Type _type, std::string _nsName, lsn_t _lsn, lsn_t _nsVersion, int _emmiterServerId); - UpdateRecord(Type _type, std::string _nsName, lsn_t _lsn, lsn_t _nsVersion, int _emmiterServerId, std::string _data); - UpdateRecord(Type _type, std::string _nsName, lsn_t _lsn, lsn_t _nsVersion, int _emmiterServerId, WrSerializer&& _data); - UpdateRecord(Type _type, std::string _nsName, lsn_t _lsn, lsn_t _nsVersion, int _emmiterServerId, const TagsMatcher& _tm); - UpdateRecord(Type _type, std::string _nsName, lsn_t _lsn, lsn_t _nsVersion, int _emmiterServerId, IndexDef _idef); - UpdateRecord(Type _type, std::string _nsName, lsn_t _nsVersion, int _emmiterServerId, NamespaceDef _def, int64_t _stateToken); - UpdateRecord(Type _type, std::string _nsName, lsn_t _lsn, lsn_t _nsVersion, int _emmiterServerId, std::string _k, std::string _v); - UpdateRecord(Type _type, int _emmiterServerId, std::string _data, int64_t sourceId); - UpdateRecord(Type _type, int _emmiterServerId, int64_t sourceId); - - const std::string& GetNsName() const noexcept { return nsName; } - bool IsDbRecord() const noexcept { - return type == Type::AddNamespace || type == Type::DropNamespace || type == Type::CloseNamespace || type == Type::RenameNamespace || - type == Type::ResyncNamespaceGeneric || type == Type::ResyncNamespaceLeaderInit || type == Type::SaveShardingConfig || - type == Type::ApplyShardingConfig || type == Type::ResetOldShardingConfig || type == Type::RollbackCandidateConfig || - type == Type::ResetCandidateConfig; - } - bool IsRequiringTmUpdate() const noexcept { - return type == Type::IndexAdd || type == Type::SetSchema || type == Type::IndexDrop || type == Type::IndexUpdate || IsDbRecord(); - } - bool IsRequiringTx() const noexcept { - return type == Type::CommitTx || type == Type::ItemUpsertTx || type == Type::ItemInsertTx || type == Type::ItemDeleteTx || - type == Type::ItemUpdateTx || type == Type::UpdateQueryTx || type == Type::DeleteQueryTx || type == Type::PutMetaTx || - type == Type::SetTagsMatcherTx; - } - bool IsTxBeginning() const noexcept { return type == Type::BeginTx; } - bool IsEmptyRecord() const noexcept { return type == Type::EmptyUpdate; } - bool IsBatchingAllowed() const noexcept { - switch (type) { - case Type::ItemUpdate: - case Type::ItemUpsert: - case Type::ItemDelete: - case Type::ItemInsert: - case Type::ItemUpdateTx: - case Type::ItemUpsertTx: - case Type::ItemDeleteTx: - case Type::ItemInsertTx: - case Type::PutMeta: - case Type::PutMetaTx: - case Type::DeleteMeta: - case Type::UpdateQueryTx: - case Type::DeleteQueryTx: - case Type::Truncate: - return true; - case Type::None: - case Type::IndexAdd: - case Type::IndexDrop: - case Type::IndexUpdate: - case Type::UpdateQuery: - case Type::DeleteQuery: - case Type::SetSchema: - case Type::BeginTx: - case Type::CommitTx: - case Type::AddNamespace: - case Type::CloseNamespace: - case Type::DropNamespace: - case Type::RenameNamespace: - case Type::ResyncNamespaceGeneric: - case Type::ResyncNamespaceLeaderInit: - case Type::ResyncOnUpdatesDrop: - case Type::EmptyUpdate: - case Type::NodeNetworkCheck: - case Type::SetTagsMatcher: - case Type::SetTagsMatcherTx: - case Type::SaveShardingConfig: - case Type::ApplyShardingConfig: - case Type::ResetOldShardingConfig: - case Type::ResetCandidateConfig: - case Type::RollbackCandidateConfig: - default: - return false; - } - } - bool IsNetworkCheckRecord() const noexcept { return type == Type::NodeNetworkCheck; } - size_t DataSize() const noexcept; - bool HasEmmiterID() const noexcept { return emmiterServerId != -1; } - UpdateRecord Clone() const; - - Type type = Type::None; - std::string nsName; - ExtendedLsn extLsn; - std::variant, std::unique_ptr, std::unique_ptr, - std::unique_ptr, std::unique_ptr, - std::unique_ptr, std::unique_ptr, - std::unique_ptr, std::unique_ptr, - std::unique_ptr, std::unique_ptr, - std::unique_ptr> - data; - int emmiterServerId = -1; -}; - -} // namespace cluster -} // namespace reindexer diff --git a/cpp_src/cmake/modules/FindSnappy.cmake b/cpp_src/cmake/modules/FindSnappy.cmake index 0a7fbe235..46c8dd992 100644 --- a/cpp_src/cmake/modules/FindSnappy.cmake +++ b/cpp_src/cmake/modules/FindSnappy.cmake @@ -60,20 +60,20 @@ find_library( if (SNAPPY_INCLUDE_DIR AND SNAPPY_LIBRARY) set(SNAPPY_FOUND TRUE) set( SNAPPY_LIBRARIES ${SNAPPY_LIBRARY} ) -else () +else() set(SNAPPY_FOUND FALSE) set( SNAPPY_LIBRARIES ) -endif () +endif() if (SNAPPY_FOUND) message(STATUS "Found Snappy: ${SNAPPY_LIBRARY}") -else () +else() message(STATUS "Not Found Snappy: ${SNAPPY_LIBRARY}") if (SNAPPY_FIND_REQUIRED) message(STATUS "Looked for Snappy libraries named ${SNAPPY_NAMES}.") message(FATAL_ERROR "Could NOT find Snappy library") - endif () -endif () + endif() +endif() mark_as_advanced( SNAPPY_LIBRARY diff --git a/cpp_src/cmake/modules/RxPrepareCpackDeps.cmake b/cpp_src/cmake/modules/RxPrepareCpackDeps.cmake index 6ac0cb17e..59118068c 100644 --- a/cpp_src/cmake/modules/RxPrepareCpackDeps.cmake +++ b/cpp_src/cmake/modules/RxPrepareCpackDeps.cmake @@ -1,28 +1,28 @@ # Packaging and install stuff for the RPM/DEB/TGZ package if(CMAKE_SYSTEM_NAME MATCHES "Linux" AND EXISTS "/etc/issue") file(READ "/etc/issue" LINUX_ISSUE) -endif () +endif() if(CMAKE_SYSTEM_NAME MATCHES "Linux" AND EXISTS "/etc/os-release") file(READ "/etc/os-release" LINUX_ISSUE) -endif () +endif() set(CPACK_GENERATOR "TGZ") if (WIN32) set (CPACK_GENERATOR "NSIS") -elseif (LINUX_ISSUE MATCHES "Fedora" OR LINUX_ISSUE MATCHES "CentOS" OR LINUX_ISSUE MATCHES "Mandriva" +elseif(LINUX_ISSUE MATCHES "Fedora" OR LINUX_ISSUE MATCHES "CentOS" OR LINUX_ISSUE MATCHES "Mandriva" OR LINUX_ISSUE MATCHES "RED OS") set(CPACK_GENERATOR "RPM") set(CPACK_PACKAGE_RELOCATABLE OFF) -elseif (LINUX_ISSUE MATCHES "altlinux") +elseif(LINUX_ISSUE MATCHES "altlinux") set(CPACK_GENERATOR "RPM") set(CPACK_PACKAGE_RELOCATABLE OFF) set(RPM_EXTRA_LIB_PREFIX "lib") -elseif (LINUX_ISSUE MATCHES "Ubuntu" OR LINUX_ISSUE MATCHES "Debian" OR LINUX_ISSUE MATCHES "Mint") +elseif(LINUX_ISSUE MATCHES "Ubuntu" OR LINUX_ISSUE MATCHES "Debian" OR LINUX_ISSUE MATCHES "Mint") set(CPACK_GENERATOR "DEB") endif() -message ("Target cpack package type was detected as '${RxPrepareCpackDeps}'") +message("Target cpack package type was detected as '${RxPrepareCpackDeps}'") SET(CPACK_PACKAGE_NAME "reindexer-4") SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "ReindexerDB server package") @@ -35,7 +35,7 @@ set(CPACK_RPM_COMPONENT_INSTALL ON) set(CPACK_DEB_COMPONENT_INSTALL ON) if (WIN32) set(CPACK_SET_DESTDIR OFF) -else () +else() set(CPACK_SET_DESTDIR ON) endif() @@ -49,12 +49,12 @@ set (CPACK_RPM_PACKAGE_REQUIRES_PRE "") if (LevelDB_LIBRARY) SET(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libleveldb-dev") SET(CPACK_RPM_PACKAGE_REQUIRES_PRE "${CPACK_RPM_PACKAGE_REQUIRES_PRE},${RPM_EXTRA_LIB_PREFIX}leveldb") -endif () +endif() if (RocksDB_LIBRARY) SET(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},librocksdb-dev") SET(CPACK_RPM_PACKAGE_REQUIRES_PRE "${CPACK_RPM_PACKAGE_REQUIRES_PRE},${RPM_EXTRA_LIB_PREFIX}rocksdb") -endif () +endif() if (Z_LIBRARY) SET(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},zlib1g-dev") @@ -74,7 +74,7 @@ endif() if (SNAPPY_FOUND) SET(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libsnappy-dev") SET(CPACK_RPM_PACKAGE_REQUIRES_PRE "${CPACK_RPM_PACKAGE_REQUIRES_PRE},${RPM_EXTRA_LIB_PREFIX}snappy") -endif () +endif() if (LIBUNWIND) SET(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libunwind-dev") @@ -84,18 +84,18 @@ if (GPERFTOOLS_TCMALLOC) SET(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgoogle-perftools4") if (LINUX_ISSUE MATCHES "altlinux") SET(CPACK_RPM_PACKAGE_REQUIRES_PRE "${CPACK_RPM_PACKAGE_REQUIRES_PRE},gperftools") - else () + else() SET(CPACK_RPM_PACKAGE_REQUIRES_PRE "${CPACK_RPM_PACKAGE_REQUIRES_PRE},gperftools-libs") - endif () -endif () + endif() +endif() # Remove first ',' from list of dependencies if (CPACK_DEBIAN_PACKAGE_DEPENDS STREQUAL "") set (CPACK_DEBIAN_DEV_PACKAGE_DEPENDS "libleveldb-dev") -else () +else() string (SUBSTRING "${CPACK_DEBIAN_PACKAGE_DEPENDS}" 1 -1 CPACK_DEBIAN_PACKAGE_DEPENDS) set (CPACK_DEBIAN_DEV_PACKAGE_DEPENDS "libleveldb-dev,${CPACK_DEBIAN_PACKAGE_DEPENDS}") -endif () +endif() if (CPACK_RPM_PACKAGE_REQUIRES_PRE STREQUAL "") set (CPACK_RPM_DEV_PACKAGE_REQUIRES_PRE "${RPM_EXTRA_LIB_PREFIX}leveldb-devel") @@ -106,12 +106,12 @@ else() foreach (DEP ${CPACK_RPM_PACKAGE_REQUIRES_LIST}) if (NOT "${DEP}" STREQUAL "gperftools-libs") list(APPEND CPACK_RPM_DEV_PACKAGE_REQUIRES_PRE "${DEP}-devel") - else () + else() list(APPEND CPACK_RPM_DEV_PACKAGE_REQUIRES_PRE "${DEP}") - endif () + endif() endforeach (DEP) string(REPLACE ";" "," CPACK_RPM_DEV_PACKAGE_REQUIRES_PRE "${CPACK_RPM_DEV_PACKAGE_REQUIRES_PRE}") -endif () +endif() set (CPACK_DEBIAN_SERVER_FILE_NAME "DEB-DEFAULT") set (CPACK_DEBIAN_DEV_FILE_NAME "DEB-DEFAULT") diff --git a/cpp_src/cmake/modules/RxPrepareInstallFiles.cmake b/cpp_src/cmake/modules/RxPrepareInstallFiles.cmake index ce4c88b18..3c4f57fd5 100644 --- a/cpp_src/cmake/modules/RxPrepareInstallFiles.cmake +++ b/cpp_src/cmake/modules/RxPrepareInstallFiles.cmake @@ -1,33 +1,33 @@ # Prepare installation files and headers if (NOT GO_BUILTIN_EXPORT_PKG_PATH) set (GO_BUILTIN_EXPORT_PKG_PATH "${PROJECT_SOURCE_DIR}/../bindings/builtin") -endif () +endif() if (NOT GO_BUILTIN_SERVER_EXPORT_PKG_PATH) set (GO_BUILTIN_SERVER_EXPORT_PKG_PATH "${PROJECT_SOURCE_DIR}/../bindings/builtinserver") -endif () +endif() if (GO_BUILTIN_EXPORT_PKG_PATH AND NOT IS_ABSOLUTE ${GO_BUILTIN_EXPORT_PKG_PATH}) set (GO_BUILTIN_EXPORT_PKG_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${GO_BUILTIN_EXPORT_PKG_PATH}") -endif () +endif() function(generate_libs_list INPUT_LIBS OUTPUT_LIST_NAME) set (flibs ${${OUTPUT_LIST_NAME}}) foreach(lib ${INPUT_LIBS}) if (${lib} MATCHES "jemalloc" OR ${lib} MATCHES "tcmalloc") - elseif (${lib} STREQUAL "-pthread") + elseif(${lib} STREQUAL "-pthread") list(APPEND flibs " -lpthread") - elseif ("${lib}" MATCHES "^\\-.*") + elseif("${lib}" MATCHES "^\\-.*") list(APPEND flibs " ${lib}") - else () + else() if (NOT "${lib}" STREQUAL "snappy" OR SNAPPY_FOUND) get_filename_component(lib ${lib} NAME_WE) string(REGEX REPLACE "^lib" "" lib ${lib}) list(APPEND flibs " -l${lib}") - else () + else() list(APPEND flibs " -l${lib}") endif() - endif () + endif() endforeach(lib) list(APPEND flibs " -lstdc++") set (${OUTPUT_LIST_NAME} ${flibs} PARENT_SCOPE) @@ -50,7 +50,7 @@ if (NOT WIN32) unset (cgo_cxx_flags) unset (cgo_c_flags) unset (cgo_ld_flags) - endif () + endif() SET(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "server") SET(DIST_INCLUDE_FILES @@ -148,5 +148,5 @@ else() @ONLY ) unset (cgo_ld_flags) - endif () -endif () + endif() +endif() diff --git a/cpp_src/cmake/modules/RxSetupLTO.cmake b/cpp_src/cmake/modules/RxSetupLTO.cmake new file mode 100644 index 000000000..a6f01f070 --- /dev/null +++ b/cpp_src/cmake/modules/RxSetupLTO.cmake @@ -0,0 +1,20 @@ +if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + include(CheckIPOSupported) + check_ipo_supported(RESULT IPO_IS_SUPPORTED OUTPUT IPO_SUPPORT_OUT) + if(IPO_IS_SUPPORTED) + message("Building with LTO...") + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # CMAKE unable to set threads count by himself + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -flto=4 -fno-fat-lto-objects -flto-odr-type-merging") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto=4 -fno-fat-lto-objects -flto-odr-type-merging") + else() + # Sets -flto-thin for Clang + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() + else() + message(WARNING "LTO was requested, but not supported: ${IPO_SUPPORT_OUT}") + endif() +else() + message(WARNING "LTO was disabled for the 'Debug' build") +endif() + diff --git a/cpp_src/cmd/reindexer_server/CMakeLists.txt b/cpp_src/cmd/reindexer_server/CMakeLists.txt index c9e97f75d..cebb83d18 100644 --- a/cpp_src/cmd/reindexer_server/CMakeLists.txt +++ b/cpp_src/cmd/reindexer_server/CMakeLists.txt @@ -26,9 +26,9 @@ add_dependencies(${TARGET} reindexer_server_library) if (APPLE) set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,@loader_path") -elseif (NOT WIN32) +elseif(NOT WIN32) set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,\$ORIGIN") -endif () +endif() target_link_libraries(${TARGET} ${REINDEXER_LIBRARIES}) @@ -57,7 +57,7 @@ if (NOT WIN32) set (REINDEXER_INSTALL_PREFIX \"${CMAKE_INSTALL_PREFIX}\") else() set (REINDEXER_INSTALL_PREFIX \"\${CMAKE_INSTALL_PREFIX}\") - endif () + endif() endif() configure_file(${PROJECT_SOURCE_DIR}/contrib/config.yml.in ${PROJECT_BINARY_DIR}/contrib/config.yml) ") @@ -83,8 +83,8 @@ if (WIN32) install(FILES ${MINGW_DLL_DIR}/libgcc_s_seh-1.dll DESTINATION ${CMAKE_INSTALL_BINDIR}) elseif(EXISTS ${MINGW_DLL_DIR}/libgcc_s_dw2-1.dll) install(FILES ${MINGW_DLL_DIR}/libgcc_s_dw2-1.dll DESTINATION ${CMAKE_INSTALL_BINDIR}) - else () - message (WARNING "Can't find MinGW runtime") + else() + message(WARNING "Can't find MinGW runtime") endif() endif() diff --git a/cpp_src/cmd/reindexer_server/contrib/entrypoint.sh b/cpp_src/cmd/reindexer_server/contrib/entrypoint.sh index 935d04c49..7906c5fea 100755 --- a/cpp_src/cmd/reindexer_server/contrib/entrypoint.sh +++ b/cpp_src/cmd/reindexer_server/contrib/entrypoint.sh @@ -28,6 +28,10 @@ if [ -n "$RX_DISABLE_NS_LEAK" ]; then RX_ARGS="$RX_ARGS --disable-ns-leak" fi +if [ -n "$RX_MAX_HTTP_REQ" ]; then + RX_ARGS="$RX_ARGS --max-http-req $RX_MAX_HTTP_REQ" +fi + if [ -n "$RX_HTTP_READ_TIMEOUT" ]; then RX_ARGS="$RX_ARGS --http-read-timeout $RX_HTTP_READ_TIMEOUT" fi diff --git a/cpp_src/cmd/reindexer_tool/CMakeLists.txt b/cpp_src/cmd/reindexer_tool/CMakeLists.txt index 18f713019..4b39f2425 100644 --- a/cpp_src/cmd/reindexer_tool/CMakeLists.txt +++ b/cpp_src/cmd/reindexer_tool/CMakeLists.txt @@ -8,46 +8,46 @@ if (WITH_CPPTRACE) list(APPEND REINDEXER_LIBRARIES cpptrace ${REINDEXER_LIBRARIES}) endif() -if(MSVC) +if (MSVC) set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -DEBUG") endif() -if (NOT WITH_STDLIB_DEBUG) +if (NOT MSVC AND NOT WITH_STDLIB_DEBUG) find_library(ReplXX_LIBRARY NAMES ${ReplXX_NAMES} replxx) find_path(ReplXX_INCLUDE_DIR NAMES replxx.hxx HINTS /opt/local/include /usr/local/include /usr/include) if (NOT ReplXX_LIBRARY OR NOT ReplXX_INCLUDE_DIR) - # replxx not found. Download it - message (STATUS "ReplXX not found. Will download it") + # replxx not found. Download it + message(STATUS "ReplXX not found. Will download it") ExternalProject_Add( replxx_lib GIT_REPOSITORY "https://github.com/Restream/replxx" GIT_TAG "b50b7b7a8c2835b45607cffabc18e4742072e9e6" CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR} ) - include_directories (${CMAKE_CURRENT_BINARY_DIR}/include) + include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) link_directories(${CMAKE_CURRENT_BINARY_DIR}/lib) list(APPEND REINDEXER_LIBRARIES replxx) add_definitions(-DREINDEX_WITH_REPLXX) - else () - message (STATUS "Found ReplXX: ${ReplXX_LIBRARY}") + else() + message(STATUS "Found ReplXX: ${ReplXX_LIBRARY}") include_directories(${ReplXX_INCLUDE_DIR}) list(APPEND REINDEXER_LIBRARIES ${ReplXX_LIBRARY}) add_definitions(-DREINDEX_WITH_REPLXX) - endif () -endif () + endif() +endif() file(GLOB_RECURSE SRCS *.h *.cc) add_executable(${TARGET} ${SRCS}) -# Enable export to provide readble stacktraces +# Enable export to provide readable stacktraces set_property(TARGET ${TARGET} PROPERTY ENABLE_EXPORTS 1) -if (NOT WITH_STDLIB_DEBUG) +if (NOT MSVC AND NOT WITH_STDLIB_DEBUG) if (NOT ReplXX_LIBRARY OR NOT ReplXX_INCLUDE_DIR) add_dependencies(${TARGET} replxx_lib) - endif () -endif () + endif() +endif() target_link_libraries(${TARGET} ${REINDEXER_LIBRARIES} ) @@ -57,7 +57,7 @@ install(TARGETS ${TARGET} ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) -if(MSVC) +if (MSVC) install(FILES $ DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) endif() diff --git a/cpp_src/cmd/reindexer_tool/commandsexecutor.cc b/cpp_src/cmd/reindexer_tool/commandsexecutor.cc index 26d7a7ba2..960881d93 100644 --- a/cpp_src/cmd/reindexer_tool/commandsexecutor.cc +++ b/cpp_src/cmd/reindexer_tool/commandsexecutor.cc @@ -523,7 +523,7 @@ Error CommandsExecutor::processImpl(const std::string& command) noe if (!token.length()) return Error(); if (fromFile_ && reindexer::checkIfStartsWith(kDumpModePrefix, command)) { DumpOptions opts; - auto err = opts.FromJSON(command.substr(kDumpModePrefix.size())); + auto err = opts.FromJSON(reindexer::giftStr(command.substr(kDumpModePrefix.size()))); if (!err.ok()) return Error(errParams, "Unable to parse dump mode from cmd: %s", err.what()); dumpMode_ = opts.mode; } @@ -918,7 +918,7 @@ Error CommandsExecutor::commandNamespaces(const std::string& comman std::string_view subCommand = parser.NextToken(); if (iequals(subCommand, "add")) { - auto nsName = reindexer::unescapeString(parser.NextToken()); + parser.NextToken(); // nsName NamespaceDef def(""); Error err = def.FromJSON(reindexer::giftStr(parser.CurPtr())); @@ -1087,9 +1087,10 @@ Error CommandsExecutor::commandBench(const std::string& command) { std::atomic count(0), errCount(0); auto worker = std::bind(getBenchWorkerFn(count, errCount), deadline); - auto threads = std::unique_ptr(new std::thread[numThreads_]); - for (int i = 0; i < numThreads_; i++) threads[i] = std::thread(worker); - for (int i = 0; i < numThreads_; i++) threads[i].join(); + const auto numThreads = std::min(std::max(numThreads_, 1u), 65535u); + auto threads = std::unique_ptr(new std::thread[numThreads]); + for (unsigned i = 0; i < numThreads; i++) threads[i] = std::thread(worker); + for (unsigned i = 0; i < numThreads; i++) threads[i].join(); output_() << "Done. Got " << count / benchTime << " QPS, " << errCount << " errors" << std::endl; return err; diff --git a/cpp_src/cmd/reindexer_tool/commandsexecutor.h b/cpp_src/cmd/reindexer_tool/commandsexecutor.h index b674e93bd..99848743f 100644 --- a/cpp_src/cmd/reindexer_tool/commandsexecutor.h +++ b/cpp_src/cmd/reindexer_tool/commandsexecutor.h @@ -42,7 +42,7 @@ class CommandsExecutor { }; template - CommandsExecutor(const std::string& outFileName, int numThreads, Args... args) + CommandsExecutor(const std::string& outFileName, unsigned numThreads, Args... args) : db_(std::move(args)...), output_(outFileName), numThreads_(numThreads) {} CommandsExecutor(const CommandsExecutor&) = delete; CommandsExecutor(CommandsExecutor&&) = delete; @@ -245,7 +245,7 @@ class CommandsExecutor { CancelContext cancelCtx_; DBInterface db_; Output output_; - int numThreads_; + unsigned numThreads_ = 1; std::unordered_map variables_; URI uri_; reindexer::net::ev::async cmdAsync_; diff --git a/cpp_src/cmd/reindexer_tool/commandsprocessor.cc b/cpp_src/cmd/reindexer_tool/commandsprocessor.cc index f7a086af1..4d728125d 100644 --- a/cpp_src/cmd/reindexer_tool/commandsprocessor.cc +++ b/cpp_src/cmd/reindexer_tool/commandsprocessor.cc @@ -16,7 +16,9 @@ Error CommandsProcessor::Connect(const std::string& dsn, const Args template CommandsProcessor::~CommandsProcessor() { - stop(); + if (auto err = stop(); !err.ok()) { + std::cerr << "Error during CommandsProcessor's termination: " << err.what() << std::endl; + } } template @@ -154,7 +156,7 @@ bool CommandsProcessor::Run(const std::string& command, const std:: if (!inFileName_.empty()) { std::ifstream infile(inFileName_); if (!infile) { - std::cerr << "ERROR: Can't open " << inFileName_ << std::endl; + std::cerr << "ERROR: Can't open " << inFileName_ << ", " << strerror(errno) << std::endl; return false; } return fromFile(infile); diff --git a/cpp_src/cmd/reindexer_tool/commandsprocessor.h b/cpp_src/cmd/reindexer_tool/commandsprocessor.h index fd52ed5c6..331603737 100644 --- a/cpp_src/cmd/reindexer_tool/commandsprocessor.h +++ b/cpp_src/cmd/reindexer_tool/commandsprocessor.h @@ -17,7 +17,7 @@ template class CommandsProcessor { public: template - CommandsProcessor(const std::string& outFileName, const std::string& inFileName, int numThreads, Args... args) + CommandsProcessor(const std::string& outFileName, const std::string& inFileName, unsigned numThreads, Args... args) : inFileName_(inFileName), executor_(outFileName, numThreads, std::move(args)...) {} CommandsProcessor(const CommandsProcessor&) = delete; CommandsProcessor(CommandsProcessor&&) = delete; diff --git a/cpp_src/cmd/reindexer_tool/reindexer_tool.cc b/cpp_src/cmd/reindexer_tool/reindexer_tool.cc index 431425401..420c64367 100644 --- a/cpp_src/cmd/reindexer_tool/reindexer_tool.cc +++ b/cpp_src/cmd/reindexer_tool/reindexer_tool.cc @@ -75,8 +75,8 @@ int main(int argc, char* argv[]) { "dump mode for sharded databases: 'full_node' (default), 'sharded_only', 'local_only'", {"dump-mode"}, "", Options::Single | Options::Global); - args::ValueFlag connThreads(progOptions, "INT", "Number of threads(connections) used by db connector", {'t', "threads"}, 1, - Options::Single | Options::Global); + args::ValueFlag connThreads(progOptions, "INT=1..65535", "Number of threads(connections) used by db connector", + {'t', "threads"}, 1, Options::Single | Options::Global); args::Flag createDBF(progOptions, "", "Enable created database if missed", {"createdb"}); @@ -166,7 +166,6 @@ int main(int argc, char* argv[]) { err = commandsProcessor.Connect(dsn, reindexer::client::ConnectOpts().CreateDBIfMissing(createDBF && args::get(createDBF))); if (err.ok()) ok = commandsProcessor.Run(args::get(command), args::get(dumpMode)); } else if (checkIfStartsWithCS("builtin://"sv, dsn)) { - reindexer::Reindexer db; CommandsProcessor commandsProcessor(args::get(outFileName), args::get(fileName), args::get(connThreads)); err = commandsProcessor.Connect(dsn, ConnectOpts().DisableReplication()); if (err.ok()) ok = commandsProcessor.Run(args::get(command), args::get(dumpMode)); diff --git a/cpp_src/cmd/reindexer_tool/repair_tool.cc b/cpp_src/cmd/reindexer_tool/repair_tool.cc index 2daae4555..94da9d019 100644 --- a/cpp_src/cmd/reindexer_tool/repair_tool.cc +++ b/cpp_src/cmd/reindexer_tool/repair_tool.cc @@ -2,13 +2,10 @@ #include #include "core/namespace/namespaceimpl.h" #include "core/storage/storagefactory.h" +#include "events/observer.h" #include "iotools.h" #include "tools/fsops.h" -#ifdef REINDEX_WITH_V3_FOLLOWERS -#include "replv3/updatesobserver.h" -#endif // REINDEX_WITH_V3_FOLLOWERS - namespace reindexer_tool { const char kStoragePlaceholderFilename[] = ".reindexer.storage"; @@ -65,30 +62,21 @@ Error RepairTool::repairNamespace(IDataStorage *storage, const std::string &stor if (!reindexer::validateObjectName(name, true)) { return Error(errParams, "Namespace name contains invalid character. Only alphas, digits,'_','-', are allowed"); } - class DummyClusterizator final : public reindexer::cluster::INsDataReplicator { - Error Replicate(reindexer::cluster::UpdateRecord &&, std::function f, const reindexer::RdxContext &) override { - f(); - return {}; - } + class DummyClusterizator final : public reindexer::cluster::IDataReplicator, public reindexer::cluster::IDataSyncer { Error Replicate(reindexer::cluster::UpdatesContainer &&, std::function f, const reindexer::RdxContext &) override { f(); return {}; } - Error ReplicateAsync(reindexer::cluster::UpdateRecord &&, const reindexer::RdxContext &) override { return {}; } Error ReplicateAsync(reindexer::cluster::UpdatesContainer &&, const reindexer::RdxContext &) override { return {}; } - void AwaitInitialSync(std::string_view, const reindexer::RdxContext &) const override {} + void AwaitInitialSync(const reindexer::NamespaceName &, const reindexer::RdxContext &) const override {} void AwaitInitialSync(const reindexer::RdxContext &) const override {} - bool IsInitialSyncDone(std::string_view) const override { return true; } + bool IsInitialSyncDone(const reindexer::NamespaceName &) const override { return true; } bool IsInitialSyncDone() const override { return true; } }; DummyClusterizator dummyClusterizator; -#ifdef REINDEX_WITH_V3_FOLLOWERS - reindexer::UpdatesObservers observers; + reindexer::UpdatesObservers observers("repair_db", dummyClusterizator, 0); reindexer::NamespaceImpl ns(name, {}, dummyClusterizator, observers); -#else // REINDEX_WITH_V3_FOLLOWERS - reindexer::NamespaceImpl ns(name, {}, dummyClusterizator); -#endif // REINDEX_WITH_V3_FOLLOWERS StorageOpts storageOpts; reindexer::RdxContext dummyCtx; std::cout << "Loading " << name << std::endl; @@ -103,7 +91,7 @@ Error RepairTool::repairNamespace(IDataStorage *storage, const std::string &stor if (input == "y" || input == "yes") { auto res = reindexer::fs::RmDirAll(nsPath); if (res < 0) { - std::cerr << "Namespace rm error[" << nsPath << "]: %s" << strerror(errno) << std::endl; + std::cerr << "Namespace rm error[" << nsPath << "]: " << strerror(errno) << std::endl; } break; } else if (input == "n" || input == "no" || input.empty()) { diff --git a/cpp_src/core/activity_context.cc b/cpp_src/core/activity_context.cc index 5b4186eff..7c94d260c 100644 --- a/cpp_src/core/activity_context.cc +++ b/cpp_src/core/activity_context.cc @@ -95,11 +95,7 @@ RdxActivityContext::RdxActivityContext(std::string_view activityTracer, std::str : data_{nextId(), ipConnectionId, std::string(activityTracer), std::string(user), std::string(query), system_clock_w::now(), Activity::InProgress, ""sv}, state_(serializeState(clientState ? Activity::Sending : Activity::InProgress)), -#ifndef NDEBUG - refCount_(0u), -#endif parent_(&parent) - { parent_->Register(this); } @@ -108,10 +104,8 @@ RdxActivityContext::RdxActivityContext(std::string_view activityTracer, std::str RdxActivityContext::RdxActivityContext(RdxActivityContext&& other) : data_(other.data_), state_(other.state_.load(std::memory_order_relaxed)), -#ifndef NDEBUG - refCount_(0u), -#endif - parent_(other.parent_) { + parent_(other.parent_) +{ if (parent_) parent_->Reregister(&other, this); other.parent_ = nullptr; } diff --git a/cpp_src/core/cbinding/reindexer_c.cc b/cpp_src/core/cbinding/reindexer_c.cc index 63a6c66f7..3351b922a 100644 --- a/cpp_src/core/cbinding/reindexer_c.cc +++ b/cpp_src/core/cbinding/reindexer_c.cc @@ -10,17 +10,18 @@ #include "core/cjson/baseencoder.h" #include "debug/crashqueryreporter.h" #include "estl/syncpool.h" +#include "events/subscriber_config.h" #include "reindexer_version.h" +#include "reindexer_wrapper.h" #include "resultserializer.h" -#include "tools/logger.h" #include "tools/semversion.h" using namespace reindexer; -const int kQueryResultsPoolSize = 1024; -const int kMaxConcurentQueries = 65534; -const size_t kCtxArrSize = 1024; -const size_t kWarnLargeResultsLimit = 0x40000000; -const size_t kMaxPooledResultsCap = 0x10000; +constexpr int kQueryResultsPoolSize = 1024; +constexpr int kMaxConcurentQueries = 65534; +constexpr size_t kCtxArrSize = 1024; +constexpr size_t kWarnLargeResultsLimit = 0x40000000; +constexpr size_t kMaxPooledResultsCap = 0x10000; static const Error err_not_init(errNotValid, "Reindexer db has not initialized"); static const Error err_too_many_queries(errLogic, "Too many parallel queries"); @@ -47,6 +48,30 @@ static reindexer_ret ret2c(const Error& err_, const reindexer_resbuffer& out) { return ret; } +static reindexer_array_ret arr_ret2c(const Error& err_, reindexer_buffer* out, uint32_t out_size) { + reindexer_array_ret ret; + ret.err_code = err_.code(); + if (ret.err_code) { + ret.out_buffers = 0; + ret.out_size = 0; + ret.data = uintptr_t(err_.what().length() ? strdup(err_.what().c_str()) : nullptr); + } else { + ret.out_buffers = out; + ret.out_size = out_size; + assertrx_dbg(ret.out_buffers); + } + return ret; +} + +static uint32_t span2arr(span d, reindexer_buffer* out, uint32_t out_size) { + const auto sz = std::min(d.size(), size_t(out_size)); + for (uint32_t i = 0; i < sz; ++i) { + out[i].data = d[i].data(); + out[i].len = d[i].len(); + } + return sz; +} + static std::string str2c(reindexer_string gs) { return std::string(reinterpret_cast(gs.p), gs.n); } static std::string_view str2cv(reindexer_string gs) { return std::string_view(reinterpret_cast(gs.p), gs.n); } @@ -136,24 +161,28 @@ static void results2c(std::unique_ptr result, struct reinde uintptr_t init_reindexer() { reindexer_init_locale(); - Reindexer* db = new Reindexer(); + static std::atomic dbsCounter = {0}; + ReindexerWrapper* db = new ReindexerWrapper(std::move(ReindexerConfig().WithDBName(fmt::sprintf("builtin_db_%d", dbsCounter++)))); return reinterpret_cast(db); } uintptr_t init_reindexer_with_config(reindexer_config config) { reindexer_init_locale(); - Reindexer* db = - new Reindexer(ReindexerConfig().WithAllocatorCacheLimits(config.allocator_cache_limit, config.allocator_max_cache_part)); + ReindexerWrapper* db = + new ReindexerWrapper(std::move(ReindexerConfig() + .WithAllocatorCacheLimits(config.allocator_cache_limit, config.allocator_max_cache_part) + .WithDBName(str2c(config.sub_db_name)) + .WithUpdatesSize(config.max_updates_size))); return reinterpret_cast(db); } void destroy_reindexer(uintptr_t rx) { - auto db = reinterpret_cast(rx); + auto db = reinterpret_cast(rx); delete db; } reindexer_error reindexer_ping(uintptr_t rx) { - auto db = reinterpret_cast(rx); + auto db = reinterpret_cast(rx); return error2c(db ? Error(errOK) : err_not_init); } @@ -182,7 +211,7 @@ static void procces_packed_item(Item& item, int /*mode*/, int state_token, reind } reindexer_error reindexer_modify_item_packed_tx(uintptr_t rx, uintptr_t tr, reindexer_buffer args, reindexer_buffer data) { - auto db = reinterpret_cast(rx); + auto db = reinterpret_cast(rx); TransactionWrapper* trw = reinterpret_cast(tr); if (!db) { return error2c(err_not_init); @@ -199,7 +228,7 @@ reindexer_error reindexer_modify_item_packed_tx(uintptr_t rx, uintptr_t tr, rein auto item = trw->tr_.NewItem(); procces_packed_item(item, mode, state_token, data, format, err); if (err.code() == errTagsMissmatch) { - item = db->NewItem(trw->tr_.GetNsName()); + item = db->rx.NewItem(trw->tr_.GetNsName()); err = item.Status(); if (err.ok()) { procces_packed_item(item, mode, state_token, data, format, err); @@ -308,13 +337,13 @@ reindexer_ret reindexer_modify_item_packed(uintptr_t rx, reindexer_buffer args, } reindexer_tx_ret reindexer_start_transaction(uintptr_t rx, reindexer_string nsName) { - auto db = reinterpret_cast(rx); + auto db = reinterpret_cast(rx); reindexer_tx_ret ret{0, {nullptr, 0}}; if (!db) { ret.err = error2c(err_not_init); return ret; } - Transaction tr = db->NewTransaction(str2cv(nsName)); + Transaction tr = db->rx.NewTransaction(str2cv(nsName)); if (tr.Status().ok()) { auto trw = new TransactionWrapper(std::move(tr)); ret.tx_id = reinterpret_cast(trw); @@ -325,7 +354,7 @@ reindexer_tx_ret reindexer_start_transaction(uintptr_t rx, reindexer_string nsNa } reindexer_error reindexer_rollback_transaction(uintptr_t rx, uintptr_t tr) { - auto db = reinterpret_cast(rx); + auto db = reinterpret_cast(rx); if (!db) { return error2c(err_not_init); } @@ -333,7 +362,7 @@ reindexer_error reindexer_rollback_transaction(uintptr_t rx, uintptr_t tr) { if (!trw) { return error2c(errOK); } - auto err = db->RollBackTransaction(trw->tr_); + auto err = db->rx.RollBackTransaction(trw->tr_); return error2c(err); } @@ -478,10 +507,10 @@ reindexer_error reindexer_connect_v4(uintptr_t rx, reindexer_string dsn, Connect } } - Reindexer* db = reinterpret_cast(rx); + auto db = reinterpret_cast(rx); if (!db) return error2c(err_not_init); - Error err = db->Connect(str2c(dsn), opts); - if (err.ok() && db->NeedTraceActivity()) db->SetActivityTracer("builtin", ""); + Error err = db->rx.Connect(str2c(dsn), opts); + if (err.ok() && db->rx.NeedTraceActivity()) db->rx.SetActivityTracer("builtin", ""); bindingCaps.store(caps, std::memory_order_relaxed); return error2c(err); } @@ -492,10 +521,10 @@ reindexer_error reindexer_connect(uintptr_t rx, reindexer_string dsn, ConnectOpt } reindexer_error reindexer_init_system_namespaces(uintptr_t rx) { - Reindexer* db = reinterpret_cast(rx); + auto db = reinterpret_cast(rx); if (!db) return error2c(err_not_init); - Error err = db->InitSystemNamespaces(); - if (err.ok() && db->NeedTraceActivity()) db->SetActivityTracer("builtin", ""); + Error err = db->rx.InitSystemNamespaces(); + if (err.ok() && db->rx.NeedTraceActivity()) db->rx.SetActivityTracer("builtin", ""); return error2c(err); } @@ -633,7 +662,7 @@ reindexer_ret reindexer_update_query(uintptr_t rx, reindexer_buffer in, reindexe } reindexer_error reindexer_delete_query_tx(uintptr_t rx, uintptr_t tr, reindexer_buffer in) { - auto db = reinterpret_cast(rx); + auto db = reinterpret_cast(rx); TransactionWrapper* trw = reinterpret_cast(tr); if (!db) { return error2c(err_not_init); @@ -654,7 +683,7 @@ reindexer_error reindexer_delete_query_tx(uintptr_t rx, uintptr_t tr, reindexer_ } reindexer_error reindexer_update_query_tx(uintptr_t rx, uintptr_t tr, reindexer_buffer in) { - auto db = reinterpret_cast(rx); + auto db = reinterpret_cast(rx); TransactionWrapper* trw = reinterpret_cast(tr); if (!db) { return error2c(err_not_init); @@ -711,7 +740,7 @@ reindexer_ret reindexer_enum_meta(uintptr_t rx, reindexer_string ns, reindexer_c auto& ser = results->ser; ser.PutVarUint(keys.size()); - for (const auto &key : keys) { + for (const auto& key : keys) { ser.PutVString(key); } @@ -767,11 +796,6 @@ reindexer_error reindexer_delete_meta(uintptr_t rx, reindexer_string ns, reindex return error2c(res); } -reindexer_error reindexer_commit(uintptr_t rx, reindexer_string nsName) { - auto db = reinterpret_cast(rx); - return error2c(!db ? err_not_init : db->Commit(str2cv(nsName))); -} - void reindexer_enable_logger(void (*logWriter)(int, char*)) { logInstallWriter(logWriter, LoggerPolicy::WithLocks); } void reindexer_disable_logger() { logInstallWriter(nullptr, LoggerPolicy::WithLocks); } @@ -782,14 +806,14 @@ reindexer_error reindexer_free_buffer(reindexer_resbuffer in) { if (const auto count{serializedResultsCount.fetch_sub(1, std::memory_order_relaxed)}; count < 1) { logPrintf(LogWarning, "Too many deserialized results: count=%d, alloced=%d", count, res_pool.Alloced()); } - return error2c(Error(errOK)); + return error2c(Error()); } reindexer_error reindexer_free_buffers(reindexer_resbuffer* in, int count) { for (int i = 0; i < count; i++) { // NOLINT(*.Malloc) Memory will be deallocated by Go reindexer_free_buffer(in[i]); } - return error2c(Error(errOK)); + return error2c(Error()); } reindexer_error reindexer_cancel_context(reindexer_ctx_info ctx_info, ctx_cancel_type how) { @@ -805,7 +829,7 @@ reindexer_error reindexer_cancel_context(reindexer_ctx_info ctx_info, ctx_cancel assertrx(false); } if (ctx_pool.cancelContext(ctx_info, howCPP)) { - return error2c(Error(errOK)); + return error2c(Error()); } return error2c(Error(errParams)); } @@ -819,3 +843,57 @@ void reindexer_init_locale() { setlocale(LC_NUMERIC, "C"); }); } + +reindexer_error reindexer_subscribe(uintptr_t rx, reindexer_string optsJSON) { + auto db = reinterpret_cast(rx); + Error err = err_not_init; + if (db) { + EventSubscriberConfig cfg; + std::string json(str2cv(optsJSON)); + err = cfg.FromJSON(giftStr(json)); + if (err.ok()) { + err = db->rx.SubscribeUpdates(db->builtinUpdatesObs, std::move(cfg)); + } + } + return error2c(err); +} + +reindexer_error reindexer_unsubscribe(uintptr_t rx) { + auto db = reinterpret_cast(rx); + Error err = err_not_init; + if (db) { + err = db->rx.UnsubscribeUpdates(db->builtinUpdatesObs); + } + return error2c(err); +} + +reindexer_array_ret reindexer_read_events(uintptr_t rx, reindexer_buffer* out_buffers, uint32_t buffers_count) { + Error err = err_not_init; + reindexer_buffer* res = nullptr; + uint32_t resCount = 0; + if (out_buffers == nullptr) { + return arr_ret2c(Error(errParams, "reindexer_await_events: out_buffers can not be null"), res, resCount); + } + if (buffers_count == 0) { + return arr_ret2c(Error(errParams, "reindexer_await_events: buffers_count can not be 0"), res, resCount); + } + if (auto db = reinterpret_cast(rx); db) { + auto updates = db->builtinUpdatesObs.TryReadUpdates(); + resCount = span2arr(updates, out_buffers, buffers_count); + res = out_buffers; + err = Error(); + // FIXME: Debug this logic in case of error + } + return arr_ret2c(err, res, resCount); +} + +reindexer_error reindexer_erase_events(uintptr_t rx, uint32_t events_count) { + Error err = err_not_init; + if (auto db = reinterpret_cast(rx); db) { + if (events_count) { + db->builtinUpdatesObs.EraseUpdates(events_count); + } + err = Error(); + } + return error2c(err); +} diff --git a/cpp_src/core/cbinding/reindexer_c.h b/cpp_src/core/cbinding/reindexer_c.h index eacc455c6..a9a8c996f 100644 --- a/cpp_src/core/cbinding/reindexer_c.h +++ b/cpp_src/core/cbinding/reindexer_c.h @@ -5,8 +5,8 @@ extern "C" { #endif #include -#include "reindexer_ctypes.h" #include "core/type_consts.h" +#include "reindexer_ctypes.h" uintptr_t init_reindexer(); uintptr_t init_reindexer_with_config(reindexer_config config); @@ -56,14 +56,17 @@ void reindexer_free_cjson(reindexer_buffer b); reindexer_error reindexer_free_buffer(reindexer_resbuffer in); reindexer_error reindexer_free_buffers(reindexer_resbuffer *in, int count); -reindexer_error reindexer_commit(uintptr_t rx, reindexer_string nsName); - reindexer_ret reindexer_enum_meta(uintptr_t rx, reindexer_string ns, reindexer_ctx_info ctx_info); reindexer_error reindexer_put_meta(uintptr_t rx, reindexer_string ns, reindexer_string key, reindexer_string data, reindexer_ctx_info ctx_info); reindexer_ret reindexer_get_meta(uintptr_t rx, reindexer_string ns, reindexer_string key, reindexer_ctx_info ctx_info); reindexer_error reindexer_delete_meta(uintptr_t rx, reindexer_string ns, reindexer_string key, reindexer_ctx_info ctx_info); +reindexer_error reindexer_subscribe(uintptr_t rx, reindexer_string optsJSON); +reindexer_error reindexer_unsubscribe(uintptr_t rx); +reindexer_array_ret reindexer_read_events(uintptr_t rx, reindexer_buffer *out_buffers, uint32_t buffers_count); +reindexer_error reindexer_erase_events(uintptr_t rx, uint32_t events_count); + reindexer_error reindexer_cancel_context(reindexer_ctx_info ctx_info, ctx_cancel_type how); void reindexer_enable_logger(void (*logWriter)(int level, char *msg)); diff --git a/cpp_src/core/cbinding/reindexer_ctypes.h b/cpp_src/core/cbinding/reindexer_ctypes.h index 32e4188c7..9e1b7da18 100644 --- a/cpp_src/core/cbinding/reindexer_ctypes.h +++ b/cpp_src/core/cbinding/reindexer_ctypes.h @@ -6,11 +6,6 @@ extern "C" { #include -typedef struct reindexer_config { - int64_t allocator_cache_limit; - float allocator_max_cache_part; -} reindexer_config; - typedef struct reindexer_buffer { uint8_t *data; int len; @@ -20,7 +15,6 @@ typedef struct reindexer_resbuffer { uintptr_t results_ptr; uintptr_t data; int len; - } reindexer_resbuffer; typedef struct reindexer_error { @@ -34,11 +28,25 @@ typedef struct reindexer_string { int8_t reserved[4]; } reindexer_string; +typedef struct reindexer_config { + int64_t allocator_cache_limit; + float allocator_max_cache_part; + uint64_t max_updates_size; + reindexer_string sub_db_name; +} reindexer_config; + typedef struct reindexer_ret { reindexer_resbuffer out; int err_code; } reindexer_ret; +typedef struct reindexer_array_ret { + reindexer_buffer *out_buffers; + uint32_t out_size; + uintptr_t data; + int err_code; +} reindexer_array_ret; + typedef struct reindexer_tx_ret { uintptr_t tx_id; reindexer_error err; @@ -51,6 +59,11 @@ typedef struct reindexer_ctx_info { typedef enum { cancel_expilicitly, cancel_on_timeout } ctx_cancel_type; +typedef struct reindexer_subscription_opts { + int stream_id; + // TODO: More opts +} reindexer_subscription_opts; + #ifdef __cplusplus } #endif diff --git a/cpp_src/core/cbinding/reindexer_wrapper.h b/cpp_src/core/cbinding/reindexer_wrapper.h new file mode 100644 index 000000000..c67149ebb --- /dev/null +++ b/cpp_src/core/cbinding/reindexer_wrapper.h @@ -0,0 +1,69 @@ +#pragma once + +#include "core/reindexer.h" +#include "tools/logger.h" +#include "updatesobserver.h" + +namespace reindexer_server { +class Server; +} + +namespace reindexer { + +constexpr size_t kBuiltinUpdatesBufSize = 8192; + +struct ReindexerWrapper { + ReindexerWrapper(ReindexerConfig&& cfg) : rx(std::move(cfg)), builtinUpdatesObs(kBuiltinUpdatesBufSize) {} + ReindexerWrapper(Reindexer&& rx) : rx(std::move(rx)), builtinUpdatesObs(kBuiltinUpdatesBufSize) {} + ~ReindexerWrapper() { + if (auto err = rx.UnsubscribeUpdates(builtinUpdatesObs); !err.ok()) { + logPrintf(LogError, "Database destruction error (updates subscription): %s", err.what()); + } + } + + Reindexer rx; + BufferedUpdateObserver builtinUpdatesObs; +}; + +class WrapersMap { +public: + std::pair try_emplace(reindexer_server::Server* sptr, Reindexer* rptr) { + assertrx_dbg(rptr); + assertrx_dbg(sptr); + const auto uiRptr = reinterpret_cast(rptr); + const auto rhash = HashT()(uiRptr); + const auto uiSrvPtr = reinterpret_cast(sptr); + const auto srvHash = HashT()(uiSrvPtr); + + std::lock_guard lck(mtx_); + + auto rmapPair = map_.try_emplace_prehashed(srvHash, uiSrvPtr); + auto& rmap = rmapPair.first->second; + + if (auto it = rmap.find(uiRptr, rhash); it != rmap.end()) { + return std::make_pair(it->second.get(), false); + } + auto [it, emplaced] = + rmap.try_emplace_prehashed(rhash, uiRptr, make_intrusive>(Reindexer(*rptr))); + return std::make_pair(it->second.get(), emplaced); + } + void erase(reindexer_server::Server* sptr) noexcept { + const auto uiSptr = reinterpret_cast(sptr); + assertrx_dbg(sptr); + + std::lock_guard lck(mtx_); + map_.erase(uiSptr); + } + +private: + // Using non-atomic intrusive wrapper, because internal map_ is the only actual owner. + // It could be a simple unique_ptr, but MSVC complains on it + using HashT = std::hash; + using RMapT = fast_hash_map>, HashT>; + + mutable std::mutex mtx_; + // Values of this map will be exposed outside of the class, so they must remain unchanged after map resizing + fast_hash_map_l map_; +}; + +} // namespace reindexer diff --git a/cpp_src/core/cbinding/updatesobserver.h b/cpp_src/core/cbinding/updatesobserver.h new file mode 100644 index 000000000..6934d6cd6 --- /dev/null +++ b/cpp_src/core/cbinding/updatesobserver.h @@ -0,0 +1,28 @@ +#pragma once + +#include "estl/chunk_buf.h" +#include "events/iexternal_listener.h" +#include "events/serializer.h" + +namespace reindexer { + +class BufferedUpdateObserver : public IEventsObserver { +public: + explicit BufferedUpdateObserver(size_t capacity) : queue_(capacity), capacity_(queue_.capacity()) {} + + size_t AvailableEventsSpace() noexcept override final { return queue_.capacity() - queue_.size_atomic(); } + void SendEvent(uint32_t streamsMask, const EventsSerializationOpts &opts, const EventRecord &rec) override final { + WrSerializer ser(queue_.get_chunk()); + UpdateSerializer user(ser); + assertrx_dbg(queue_.capacity() - queue_.size() >= 1); + queue_.write(user.Serialize(streamsMask, opts, rec)); + } + [[nodiscard]] span TryReadUpdates() noexcept { return queue_.tail(); } + void EraseUpdates(size_t count) noexcept { queue_.erase_chunks(count); } + +private: + chain_buf queue_; + const size_t capacity_; +}; + +} // namespace reindexer diff --git a/cpp_src/core/cjson/baseencoder.cc b/cpp_src/core/cjson/baseencoder.cc index d49491a38..3ccfa4bf5 100644 --- a/cpp_src/core/cjson/baseencoder.cc +++ b/cpp_src/core/cjson/baseencoder.cc @@ -158,24 +158,24 @@ bool BaseEncoder::encode(ConstPayload* pl, Serializer& rdser, Builder& throw Error(errParams, "Non-array field '%s' [%d] from '%s' can only be encoded once.", f.Name(), tagField, pl->Type().Name()); } assertrx(tagField < pl->NumFields()); - int* cnt = &fieldsoutcnt_[tagField]; + int& cnt = fieldsoutcnt_[tagField]; switch (tagType) { case TAG_ARRAY: { const auto count = rdser.GetVarUint(); if (visible) { pl->Type().Field(tagField).Type().EvaluateOneOf( - [&](KeyValueType::Bool) { builder.Array(tagName, pl->GetArray(tagField).subspan(*cnt, count), *cnt); }, - [&](KeyValueType::Int) { builder.Array(tagName, pl->GetArray(tagField).subspan(*cnt, count), *cnt); }, - [&](KeyValueType::Int64) { builder.Array(tagName, pl->GetArray(tagField).subspan(*cnt, count), *cnt); }, - [&](KeyValueType::Double) { builder.Array(tagName, pl->GetArray(tagField).subspan(*cnt, count), *cnt); }, - [&](KeyValueType::String) { builder.Array(tagName, pl->GetArray(tagField).subspan(*cnt, count), *cnt); }, - [&](KeyValueType::Uuid) { builder.Array(tagName, pl->GetArray(tagField).subspan(*cnt, count), *cnt); }, + [&](KeyValueType::Bool) { builder.Array(tagName, pl->GetArray(tagField).subspan(cnt, count), cnt); }, + [&](KeyValueType::Int) { builder.Array(tagName, pl->GetArray(tagField).subspan(cnt, count), cnt); }, + [&](KeyValueType::Int64) { builder.Array(tagName, pl->GetArray(tagField).subspan(cnt, count), cnt); }, + [&](KeyValueType::Double) { builder.Array(tagName, pl->GetArray(tagField).subspan(cnt, count), cnt); }, + [&](KeyValueType::String) { builder.Array(tagName, pl->GetArray(tagField).subspan(cnt, count), cnt); }, + [&](KeyValueType::Uuid) { builder.Array(tagName, pl->GetArray(tagField).subspan(cnt, count), cnt); }, [](OneOf) noexcept { assertrx(0); abort(); }); } - (*cnt) += count; + cnt += count; break; } case TAG_NULL: @@ -190,8 +190,8 @@ bool BaseEncoder::encode(ConstPayload* pl, Serializer& rdser, Builder& case TAG_OBJECT: case TAG_UUID: objectScalarIndexes_.set(tagField); - if (visible) builder.Put(tagName, pl->Get(tagField, (*cnt)), *cnt); - ++(*cnt); + if (visible) builder.Put(tagName, pl->Get(tagField, cnt), cnt); + ++cnt; break; } } else { @@ -217,7 +217,8 @@ bool BaseEncoder::encode(ConstPayload* pl, Serializer& rdser, Builder& } else if (visible) { builder.Array(tagName, rdser, atagType, atagCount); } else { - for (size_t i = 0; i < atagCount; ++i) rdser.SkipRawVariant(KeyValueType{atagType}); + const KeyValueType kvt{atagType}; + for (size_t i = 0; i < atagCount; ++i) rdser.SkipRawVariant(kvt); } break; } @@ -239,13 +240,15 @@ bool BaseEncoder::encode(ConstPayload* pl, Serializer& rdser, Builder& case TAG_STRING: case TAG_NULL: case TAG_END: - case TAG_UUID: + case TAG_UUID: { + const KeyValueType kvt{tagType}; if (visible) { - Variant value = rdser.GetRawVariant(KeyValueType{tagType}); + Variant value = rdser.GetRawVariant(kvt); builder.Put(tagName, std::move(value), 0); } else { - rdser.SkipRawVariant(KeyValueType{tagType}); + rdser.SkipRawVariant(kvt); } + } } } @@ -302,8 +305,9 @@ bool BaseEncoder::collectTagsSizes(ConstPayload& pl, Serializer& rdser) tagsLengths_.push_back(EndArrayItem); } } else { + const KeyValueType kvt{atagType}; for (size_t i = 0; i < atagCount; i++) { - rdser.SkipRawVariant(KeyValueType{atagType}); + rdser.SkipRawVariant(kvt); } } break; @@ -334,15 +338,15 @@ std::string_view BaseEncoder::getPlTuple(ConstPayload& pl) { VariantArray kref; pl.Get(0, kref); - p_string tuple(kref[0]); + std::string_view tuple(kref[0]); - if (tagsMatcher_ && tuple.size() == 0) { + if (tagsMatcher_ && tuple.empty()) { tmpPlTuple_.Reset(); buildPayloadTuple(pl, tagsMatcher_, tmpPlTuple_); return tmpPlTuple_.Slice(); } - return std::string_view(tuple); + return tuple; } template class BaseEncoder; diff --git a/cpp_src/core/cjson/baseencoder.h b/cpp_src/core/cjson/baseencoder.h index 5f6eec0ea..b57cc9acb 100644 --- a/cpp_src/core/cjson/baseencoder.h +++ b/cpp_src/core/cjson/baseencoder.h @@ -23,8 +23,8 @@ class IEncoderDatasourceWithJoins { virtual size_t GetJoinedRowsCount() const noexcept = 0; virtual size_t GetJoinedRowItemsCount(size_t rowId) const = 0; virtual ConstPayload GetJoinedItemPayload(size_t rowid, size_t plIndex) = 0; - virtual const std::string &GetJoinedItemNamespace(size_t rowid) = 0; - virtual const TagsMatcher &GetJoinedItemTagsMatcher(size_t rowid) = 0; + virtual const std::string &GetJoinedItemNamespace(size_t rowid) noexcept = 0; + virtual const TagsMatcher &GetJoinedItemTagsMatcher(size_t rowid) noexcept = 0; virtual const FieldsSet &GetJoinedItemFieldsFilter(size_t rowid) noexcept = 0; }; @@ -64,7 +64,7 @@ class BaseEncoder { std::string_view getPlTuple(ConstPayload &pl); const TagsMatcher *tagsMatcher_; - int fieldsoutcnt_[kMaxIndexes]; + std::array fieldsoutcnt_{0}; const FieldsSet *filter_; WrSerializer tmpPlTuple_; TagsPath curTagsPath_; diff --git a/cpp_src/core/cjson/cjsonbuilder.cc b/cpp_src/core/cjson/cjsonbuilder.cc index bd70aaee2..9c32c5c4d 100644 --- a/cpp_src/core/cjson/cjsonbuilder.cc +++ b/cpp_src/core/cjson/cjsonbuilder.cc @@ -31,7 +31,7 @@ CJsonBuilder CJsonBuilder::Array(int tagName, ObjType type) { return CJsonBuilder(*ser_, type, tm_, tagName); } -void CJsonBuilder::Array(int tagName, span data, int /*offset*/) { +void CJsonBuilder::Array(int tagName, span data, int /*offset*/) { ser_->PutCTag(ctag{TAG_ARRAY, tagName}); ser_->PutCArrayTag(carraytag(data.size(), TAG_UUID)); for (auto d : data) { diff --git a/cpp_src/core/cjson/cjsonbuilder.h b/cpp_src/core/cjson/cjsonbuilder.h index a64494a34..ed3d79c8d 100644 --- a/cpp_src/core/cjson/cjsonbuilder.h +++ b/cpp_src/core/cjson/cjsonbuilder.h @@ -34,28 +34,28 @@ class CJsonBuilder { } CJsonBuilder Object(std::nullptr_t) { return Object(0); } - void Array(int tagName, span data, int /*offset*/ = 0) { + void Array(int tagName, span data, int /*offset*/ = 0) { ser_->PutCTag(ctag{TAG_ARRAY, tagName}); ser_->PutCArrayTag(carraytag(data.size(), TAG_STRING)); for (auto d : data) ser_->PutVString(d); } - void Array(int tagName, span data, int offset = 0); - void Array(int tagName, span data, int /*offset*/ = 0) { + void Array(int tagName, span data, int offset = 0); + void Array(int tagName, span data, int /*offset*/ = 0) { ser_->PutCTag(ctag{TAG_ARRAY, tagName}); ser_->PutCArrayTag(carraytag(data.size(), TAG_VARINT)); for (auto d : data) ser_->PutVarint(d); } - void Array(int tagName, span data, int /*offset*/ = 0) { + void Array(int tagName, span data, int /*offset*/ = 0) { ser_->PutCTag(ctag{TAG_ARRAY, tagName}); ser_->PutCArrayTag(carraytag(data.size(), TAG_VARINT)); for (auto d : data) ser_->PutVarint(d); } - void Array(int tagName, span data, int /*offset*/ = 0) { + void Array(int tagName, span data, int /*offset*/ = 0) { ser_->PutCTag(ctag{TAG_ARRAY, tagName}); ser_->PutCArrayTag(carraytag(data.size(), TAG_BOOL)); for (auto d : data) ser_->PutBool(d); } - void Array(int tagName, span data, int /*offset*/ = 0) { + void Array(int tagName, span data, int /*offset*/ = 0) { ser_->PutCTag(ctag{TAG_ARRAY, tagName}); ser_->PutCArrayTag(carraytag(data.size(), TAG_DOUBLE)); for (auto d : data) ser_->PutDouble(d); diff --git a/cpp_src/core/cjson/cjsondecoder.cc b/cpp_src/core/cjson/cjsondecoder.cc index b8257e3d9..3c2b64014 100644 --- a/cpp_src/core/cjson/cjsondecoder.cc +++ b/cpp_src/core/cjson/cjsondecoder.cc @@ -11,6 +11,7 @@ bool CJsonDecoder::decodeCJson(Payload &pl, Serializer &rdser, WrSerializer &wrs const ctag tag = rdser.GetCTag(); TagType tagType = tag.Type(); if (tag == kCTagEnd) { + recoder.Serialize(wrser); wrser.PutCTag(kCTagEnd); return false; } @@ -70,7 +71,7 @@ bool CJsonDecoder::decodeCJson(Payload &pl, Serializer &rdser, WrSerializer &wrs [&](OneOf) { wrser.PutCTag(ctag{fieldType.ToTagType(), tagName, field}); }, - [&](OneOf) { assertrx(0); }); + [&](OneOf) { assertrx(false); }); } } } else { diff --git a/cpp_src/core/cjson/cjsondecoder.h b/cpp_src/core/cjson/cjsondecoder.h index 290edb4e7..69221c7b6 100644 --- a/cpp_src/core/cjson/cjsondecoder.h +++ b/cpp_src/core/cjson/cjsondecoder.h @@ -1,8 +1,8 @@ #pragma once +#include #include "core/cjson/tagspath.h" #include "core/payload/fieldsset.h" -#include #include "core/payload/payloadiface.h" #include "core/type_consts.h" @@ -17,8 +17,10 @@ class Recoder { [[nodiscard]] virtual TagType Type(TagType oldTagType) = 0; virtual void Recode(Serializer &, WrSerializer &) const = 0; virtual void Recode(Serializer &, Payload &, int tagName, WrSerializer &) = 0; - [[nodiscard]] virtual bool Match(int field) const noexcept = 0; - [[nodiscard]] virtual bool Match(const TagsPath &) const noexcept = 0; + [[nodiscard]] virtual bool Match(int field) noexcept = 0; + [[nodiscard]] virtual bool Match(TagType, const TagsPath &) = 0; + virtual void Serialize(WrSerializer &wrser) = 0; + virtual bool Reset() = 0; virtual ~Recoder() = default; }; @@ -91,9 +93,10 @@ class CJsonDecoder { public: RX_ALWAYS_INLINE DummyRecoder MakeCleanCopy() const noexcept { return DummyRecoder(); } RX_ALWAYS_INLINE bool Recode(Serializer &, WrSerializer &) const noexcept { return false; } - RX_ALWAYS_INLINE bool Recode(Serializer &, Payload &, [[maybe_unused]] int tagName, WrSerializer &) const noexcept { return false; } - RX_ALWAYS_INLINE TagType RegisterTagType(TagType oldTagType, [[maybe_unused]] int field) const noexcept { return oldTagType; } - RX_ALWAYS_INLINE TagType RegisterTagType(TagType oldTagType, const TagsPath &) const noexcept { return oldTagType; } + RX_ALWAYS_INLINE bool Recode(Serializer &, Payload &, int, WrSerializer &) const noexcept { return false; } + RX_ALWAYS_INLINE TagType RegisterTagType(TagType tagType, int) const noexcept { return tagType; } + RX_ALWAYS_INLINE TagType RegisterTagType(TagType tagType, const TagsPath &) const noexcept { return tagType; } + RX_ALWAYS_INLINE void Serialize(WrSerializer &) const {} }; class DefaultRecoder { public: @@ -107,20 +110,21 @@ class CJsonDecoder { } return needToRecode_; } - RX_ALWAYS_INLINE bool Recode(Serializer &s, Payload &p, int tagName, WrSerializer &wser) const { + RX_ALWAYS_INLINE bool Recode(Serializer &ser, Payload &pl, int tagName, WrSerializer &wser) const { if (needToRecode_) { - r_->Recode(s, p, tagName, wser); + r_->Recode(ser, pl, tagName, wser); } return needToRecode_; } - RX_ALWAYS_INLINE TagType RegisterTagType(TagType oldTagType, int field) { + RX_ALWAYS_INLINE TagType RegisterTagType(TagType tagType, int field) { needToRecode_ = r_->Match(field); - return needToRecode_ ? r_->Type(oldTagType) : oldTagType; + return needToRecode_ ? r_->Type(tagType) : tagType; } - RX_ALWAYS_INLINE TagType RegisterTagType(TagType oldTagType, const TagsPath &tagsPath) { - needToRecode_ = r_->Match(tagsPath); - return needToRecode_ ? r_->Type(oldTagType) : oldTagType; + RX_ALWAYS_INLINE TagType RegisterTagType(TagType tagType, const TagsPath &tagsPath) { + needToRecode_ = r_->Match(tagType, tagsPath); + return needToRecode_ ? r_->Type(tagType) : tagType; } + RX_ALWAYS_INLINE void Serialize(WrSerializer &wser) const { r_->Serialize(wser); } private: Recoder *r_; @@ -143,9 +147,9 @@ class CJsonDecoder { #ifdef RX_WITH_STDLIB_DEBUG std::abort(); #else - // Search of the indexed fields inside the object arrays is not imlpemented - // Possible implementation has noticable negative effect on 'FromCJSONPKOnly' benchmark. - // Currently we are using filter for PKs only, and PKs can not be arrays, so this code actually will never be called at the + // Search of the indexed fields inside the object arrays is not implemented + // Possible implementation has noticeable negative impact on 'FromCJSONPKOnly' benchmark. + // Currently, we are using filter for PKs only, and PKs can not be arrays, so this code actually will never be called at the // current moment decodeCJson(pl, rdSer, wrSer, DummyFilter(), recoder, NamelessTagOpt{}); #endif // RX_WITH_STDLIB_DEBUG diff --git a/cpp_src/core/cjson/cjsonmodifier.cc b/cpp_src/core/cjson/cjsonmodifier.cc index 55573f0dd..d83945efe 100644 --- a/cpp_src/core/cjson/cjsonmodifier.cc +++ b/cpp_src/core/cjson/cjsonmodifier.cc @@ -25,17 +25,16 @@ class CJsonModifier::Context { throw Error(errParams, "Array item should not be an empty value"); } } - - std::fill(std::begin(fieldsArrayOffsets), std::end(fieldsArrayOffsets), 0); + fieldsArrayOffsets.fill(0); } - bool IsForAllItems() const noexcept { return isForAllItems_; } + [[nodiscard]] bool IsForAllItems() const noexcept { return isForAllItems_; } const VariantArray &value; WrSerializer &wrser; Serializer rdser; TagsPath jsonPath; IndexedTagsPath currObjPath; - FieldModifyMode mode; + FieldModifyMode mode = FieldModeSet; bool fieldUpdated = false; bool updateArrayElements = false; const Payload *payload = nullptr; @@ -45,119 +44,112 @@ class CJsonModifier::Context { bool isForAllItems_ = false; }; -void CJsonModifier::SetFieldValue(std::string_view tuple, IndexedTagsPath fieldPath, const VariantArray &val, WrSerializer &ser, +void CJsonModifier::SetFieldValue(std::string_view tuple, const IndexedTagsPath &fieldPath, const VariantArray &val, WrSerializer &ser, const Payload &pl) { - if (fieldPath.empty()) { - throw Error(errLogic, kWrongFieldsAmountMsg); - } - tagsPath_.clear(); - Context ctx(fieldPath, val, ser, tuple, FieldModeSet, &pl); - fieldPath_ = std::move(fieldPath); + auto ctx = initState(tuple, fieldPath, val, ser, &pl, FieldModifyMode::FieldModeSet); updateFieldInTuple(ctx); if (!ctx.fieldUpdated && !ctx.IsForAllItems()) { throw Error(errParams, "[SetFieldValue] Requested field or array's index was not found"); } } -void CJsonModifier::SetObject(std::string_view tuple, IndexedTagsPath fieldPath, const VariantArray &val, WrSerializer &ser, +void CJsonModifier::SetObject(std::string_view tuple, const IndexedTagsPath &fieldPath, const VariantArray &val, WrSerializer &ser, const Payload &pl) { - if (fieldPath.empty()) { - throw Error(errLogic, kWrongFieldsAmountMsg); - } - tagsPath_.clear(); - Context ctx(fieldPath, val, ser, tuple, FieldModeSetJson, &pl); - fieldPath_ = std::move(fieldPath); + auto ctx = initState(tuple, fieldPath, val, ser, &pl, FieldModifyMode::FieldModeSetJson); buildCJSON(ctx); if (!ctx.fieldUpdated && !ctx.IsForAllItems()) { throw Error(errParams, "[SetObject] Requested field or array's index was not found"); } } -void CJsonModifier::RemoveField(std::string_view tuple, IndexedTagsPath fieldPath, WrSerializer &wrser) { +void CJsonModifier::RemoveField(std::string_view tuple, const IndexedTagsPath &fieldPath, WrSerializer &wrser) { + auto ctx = initState(tuple, fieldPath, {}, wrser, nullptr, FieldModeDrop); + dropFieldInTuple(ctx); +} + +CJsonModifier::Context CJsonModifier::initState(std::string_view tuple, const IndexedTagsPath &fieldPath, const VariantArray &val, + WrSerializer &ser, const Payload *pl, FieldModifyMode mode) { if (fieldPath.empty()) { throw Error(errLogic, kWrongFieldsAmountMsg); } tagsPath_.clear(); - Context ctx(fieldPath, {}, wrser, tuple, FieldModeDrop); - fieldPath_ = std::move(fieldPath); - dropFieldInTuple(ctx); + Context ctx(fieldPath, val, ser, tuple, mode, pl); + fieldPath_ = fieldPath; + + return ctx; } -void CJsonModifier::updateObject(Context &ctx, int tagName) { +void CJsonModifier::updateObject(Context &ctx, int tagName) const { + ctx.fieldUpdated = true; JsonDecoder jsonDecoder(tagsMatcher_); if (ctx.value.IsArrayValue()) { CJsonBuilder cjsonBuilder(ctx.wrser, ObjType::TypeArray, &tagsMatcher_, tagName); - for (size_t i = 0; i < ctx.value.size(); ++i) { + for (const auto &item : ctx.value) { auto objBuilder = cjsonBuilder.Object(nullptr); - jsonDecoder.Decode(std::string_view(ctx.value[i]), objBuilder, ctx.jsonPath); + jsonDecoder.Decode(std::string_view(item), objBuilder, ctx.jsonPath); } - } else { - assertrx(ctx.value.size() == 1); - CJsonBuilder cjsonBuilder(ctx.wrser, ObjType::TypeObject, &tagsMatcher_, tagName); - jsonDecoder.Decode(std::string_view(ctx.value.front()), cjsonBuilder, ctx.jsonPath); + return; } - ctx.fieldUpdated = true; -} -void CJsonModifier::updateField(Context &ctx, size_t idx) { - assertrx(idx < ctx.value.size()); - copyCJsonValue(kvType2Tag(ctx.value[idx].Type()), ctx.value[idx], ctx.wrser); + assertrx(ctx.value.size() == 1); + CJsonBuilder cjsonBuilder(ctx.wrser, ObjType::TypeObject, &tagsMatcher_, tagName); + jsonDecoder.Decode(std::string_view(ctx.value.front()), cjsonBuilder, ctx.jsonPath); } -void CJsonModifier::insertField(Context &ctx) { +void CJsonModifier::insertField(Context &ctx) const { ctx.fieldUpdated = true; assertrx(ctx.currObjPath.size() < fieldPath_.size()); int nestedObjects = 0; for (size_t i = ctx.currObjPath.size(); i < fieldPath_.size(); ++i) { - int tagName = fieldPath_[i].NameTag(); + const int tagName = fieldPath_[i].NameTag(); const bool finalTag = (i == fieldPath_.size() - 1); if (finalTag) { if (ctx.mode == FieldModeSetJson) { updateObject(ctx, tagName); - } else { - const int field = tagsMatcher_.tags2field(ctx.jsonPath.data(), fieldPath_.size()); - const TagType tagType = determineUpdateTagType(ctx, field); - if (field > 0) { - putCJsonRef(tagType, tagName, field, ctx.value, ctx.wrser); - } else { - putCJsonValue(tagType, tagName, ctx.value, ctx.wrser); - } + continue; } - } else { - ctx.wrser.PutCTag(ctag{TAG_OBJECT, tagName}); - ++nestedObjects; + + const int field = tagsMatcher_.tags2field(ctx.jsonPath.data(), fieldPath_.size()); + const TagType tagType = determineUpdateTagType(ctx, field); + isIndexed(field) ? putCJsonRef(tagType, tagName, field, ctx.value, ctx.wrser) + : putCJsonValue(tagType, tagName, ctx.value, ctx.wrser); + continue; } + + ctx.wrser.PutCTag(ctag{TAG_OBJECT, tagName}); + ++nestedObjects; } - while (nestedObjects-- > 0) ctx.wrser.PutCTag(kCTagEnd); + while (nestedObjects-- > 0) { + ctx.wrser.PutCTag(kCTagEnd); + } ctx.currObjPath.clear(); } -bool CJsonModifier::needToInsertField(const Context &ctx) { - if (ctx.fieldUpdated) return false; - if (fieldPath_.back().IsArrayNode()) return false; - if (ctx.currObjPath.size() < fieldPath_.size()) { - for (unsigned i = 0; i < ctx.currObjPath.size(); ++i) { - if (fieldPath_[i] != ctx.currObjPath[i]) { - return false; - } - } - if (ctx.IsForAllItems()) { - throw Error(errParams, "Unable to insert new field with 'all items ([*])' syntax"); +bool CJsonModifier::needToInsertField(const Context &ctx) const { + assertrx_throw(!fieldPath_.empty()); + if (ctx.fieldUpdated || fieldPath_.back().IsArrayNode()) return false; + if (ctx.currObjPath.size() >= fieldPath_.size()) return false; + assertrx_throw(ctx.currObjPath.size() <= fieldPath_.size()); + for (unsigned i = 0; i < ctx.currObjPath.size(); ++i) { + if (fieldPath_[i] != ctx.currObjPath[i]) { + return false; } - for (unsigned i = ctx.currObjPath.size(); i < fieldPath_.size(); ++i) { - if (fieldPath_[i].IsArrayNode()) { - return false; - } + } + if (ctx.IsForAllItems()) { + throw Error(errParams, "Unable to insert new field with 'all items ([*])' syntax"); + } + for (unsigned i = ctx.currObjPath.size(); i < fieldPath_.size(); ++i) { + if (fieldPath_[i].IsArrayNode()) { + return false; } - return true; } - return false; + return true; } -TagType CJsonModifier::determineUpdateTagType(const Context &ctx, int field) { - if (field >= 0) { +TagType CJsonModifier::determineUpdateTagType(const Context &ctx, int field) const { + if (isIndexed(field)) { const PayloadFieldType &fieldType = pt_.Field(field); if (!fieldType.IsArray() || ctx.updateArrayElements || !ctx.value.IsNullValue()) { for (auto &v : ctx.value) { @@ -167,14 +159,6 @@ TagType CJsonModifier::determineUpdateTagType(const Context &ctx, int field) { } } } - } else if (ctx.value.size() > 1) { - const auto type = kvType2Tag(ctx.value.front().Type()); - for (auto it = ctx.value.begin() + 1, end = ctx.value.end(); it != end; ++it) { - if (type != kvType2Tag(it->Type())) { - throw Error(errParams, "Unable to update field with heterogeneous array. Type[0] is [%s] and type[%d] is [%s]", - TagTypeToStr(type), it - ctx.value.begin(), TagTypeToStr(kvType2Tag(it->Type()))); - } - } } if (ctx.updateArrayElements || ctx.value.IsArrayValue()) { @@ -182,24 +166,198 @@ TagType CJsonModifier::determineUpdateTagType(const Context &ctx, int field) { } else if (ctx.value.IsNullValue() || ctx.value.empty()) { return TAG_NULL; } - return kvType2Tag(ctx.value.front().Type()); + return arrayKvType2Tag(ctx.value); } -bool CJsonModifier::checkIfFoundTag(Context &ctx, bool isLastItem) { - if (tagsPath_.empty()) return false; - bool result = fieldPath_.Compare(tagsPath_); - if (result) { - if (fieldPath_.back().IsArrayNode()) { - if (fieldPath_.back().IsForAllItems()) { - if (isLastItem) ctx.fieldUpdated = true; - } else { - ctx.fieldUpdated = true; +bool CJsonModifier::checkIfFoundTag(Context &ctx, bool isLastItem) const { + if (tagsPath_.empty() || !fieldPath_.Compare(tagsPath_)) return false; + + const auto &backFieldPath = fieldPath_.back(); + if (!backFieldPath.IsArrayNode() || ((!backFieldPath.IsForAllItems() || isLastItem))) { + ctx.fieldUpdated = true; + } + + return true; +} + +void CJsonModifier::setArray(Context &ctx) const { + auto type = arrayKvType2Tag(ctx.value); + ctx.wrser.PutCArrayTag(carraytag{ctx.value.size(), type}); + const bool isObjsArr = (type == TAG_OBJECT); + for (const auto &item : ctx.value) { + if (isObjsArr) { + type = item.Type().ToTagType(); + ctx.wrser.PutCTag(ctag{type}); + } + copyCJsonValue(type, item, ctx.wrser); + } +} + +void CJsonModifier::writeCTag(const ctag &tag, Context &ctx) { + bool tagMatched = checkIfFoundTag(ctx); + const TagType tagType = tag.Type(); + const int field = tag.Field(); + const int tagName = tag.Name(); + if (tagType == TAG_ARRAY) { + const auto count = ctx.rdser.GetVarUint(); + if (!tagMatched || !ctx.fieldUpdated) { + auto &lastTag = tagsPath_.back(); + for (uint64_t i = 0; i < count; ++i) { + lastTag.SetIndex(i); + const bool isLastItem = (i + 1 == count); + tagMatched = checkIfFoundTag(ctx, isLastItem); + if (tagMatched && ctx.fieldUpdated) { + break; + } + } + } + + if (tagMatched && ctx.fieldUpdated) { + const auto resultTagType = determineUpdateTagType(ctx, field); + ctx.wrser.PutCTag(ctag{resultTagType, tagName, field}); + if (resultTagType == TAG_ARRAY) { + ctx.wrser.PutVarUint(ctx.updateArrayElements ? count : ctx.value.size()); + } + return; + } + + ctx.wrser.PutCTag(ctag{tagType, tagName, field}); + ctx.wrser.PutVarUint(count); + return; + } + + if (!tagMatched) { + ctx.wrser.PutCTag(ctag{tagType, tagName, field}); + return; + } + + if (ctx.updateArrayElements) { + throw Error(errParams, "Unable to update scalar value by index"); + } + const auto resultTagType = determineUpdateTagType(ctx, field); + ctx.wrser.PutCTag(ctag{resultTagType, tagName, field}); + if (resultTagType == TAG_ARRAY) { + ctx.wrser.PutVarUint(ctx.value.size()); + } +} + +void CJsonModifier::updateArray(TagType atagType, uint32_t count, int tagName, Context &ctx) { + assertrx_throw(!ctx.value.IsArrayValue()); // Unable to update array's element with array-value + + Variant value; + if (!ctx.value.empty()) { + value = ctx.value.front(); + } + + // situation is possible when array was homogeneous, and new element of different type is added + // in this case array must change type and become heterogeneous + const auto valueType = value.Type().ToTagType(); + assertrx((atagType != valueType) || (atagType != TAG_OBJECT)); + + ctx.wrser.PutCArrayTag(carraytag{count, TAG_OBJECT}); + + for (uint32_t i = 0; i < count; i++) { + tagsPath_.back().SetIndex(i); + const bool isLastItem = (i + 1 == count); + if (checkIfFoundTag(ctx, isLastItem)) { + (atagType == TAG_OBJECT) ? skipCjsonTag(ctag{ctx.rdser.GetCTag().Type()}, ctx.rdser, &ctx.fieldsArrayOffsets) + : skipCjsonTag(ctag{atagType}, ctx.rdser, &ctx.fieldsArrayOffsets); + ctx.wrser.PutCTag(ctag{valueType}); + copyCJsonValue(valueType, value, ctx.wrser); + + ctx.fieldUpdated = true; + continue; // next item + } + + switch (atagType) { + case TAG_OBJECT: { + TagsPathScope pathScopeObj(ctx.currObjPath, tagName, i); + updateFieldInTuple(ctx); + break; + } + case TAG_VARINT: + case TAG_DOUBLE: + case TAG_STRING: + case TAG_ARRAY: + case TAG_NULL: + case TAG_BOOL: + case TAG_END: + case TAG_UUID: + // array tag type updated (need store as object) + ctx.wrser.PutCTag(ctag{atagType}); + copyCJsonValue(atagType, ctx.rdser, ctx.wrser); + break; + } + } + + assertrx_throw(ctx.fieldUpdated); +} + +void CJsonModifier::copyArray(int tagName, Context &ctx) { + const carraytag atag = ctx.rdser.GetCArrayTag(); + const TagType atagType = atag.Type(); + const auto count = atag.Count(); + + // store position in serializer + const auto rdserPos = ctx.rdser.Pos(); + const auto wrserLen = ctx.wrser.Len(); + + ctx.wrser.PutCArrayTag(atag); + + for (uint32_t i = 0; i < count; i++) { + tagsPath_.back().SetIndex(i); + const bool isLastItem = (i + 1 == count); + // update item + if (checkIfFoundTag(ctx, isLastItem)) { + if (ctx.value.IsArrayValue()) { + throw Error(errParams, "Unable to update array's element with array-value"); + } + Variant value; + if (!ctx.value.empty()) { + value = ctx.value.front(); } - } else { + // situation is possible when array was homogeneous, and new element of different type is added + const auto valueType = value.Type().ToTagType(); + if ((atagType != valueType) && (atagType != TAG_OBJECT)) { + // back to beginning of array and rewrite as an array of objects + ctx.rdser.SetPos(rdserPos); + ctx.wrser.Reset(wrserLen); + updateArray(atagType, count, tagName, ctx); + return; // array updated - stop processing + } + + // type of array not changed - simple rewrite item + auto vtagType = atagType; + if (atagType == TAG_OBJECT) { + vtagType = ctx.rdser.GetCTag().Type(); + ctx.wrser.PutCTag(ctag{valueType}); + } + skipCjsonTag(ctag{vtagType}, ctx.rdser, &ctx.fieldsArrayOffsets); + copyCJsonValue(valueType, value, ctx.wrser); + ctx.fieldUpdated = true; + continue; // next item + } + + // copy item as is + switch (atagType) { + case TAG_OBJECT: { + TagsPathScope pathScopeObj(ctx.currObjPath, tagName, i); + updateFieldInTuple(ctx); + break; + } + case TAG_VARINT: + case TAG_DOUBLE: + case TAG_STRING: + case TAG_ARRAY: + case TAG_NULL: + case TAG_BOOL: + case TAG_END: + case TAG_UUID: + copyCJsonValue(atagType, ctx.rdser, ctx.wrser); + break; } } - return result; } bool CJsonModifier::updateFieldInTuple(Context &ctx) { @@ -210,118 +368,48 @@ bool CJsonModifier::updateFieldInTuple(Context &ctx) { ctx.wrser.PutCTag(kCTagEnd); return false; } - TagType tagType = tag.Type(); + const TagType tagType = tag.Type(); const int field = tag.Field(); const int tagName = tag.Name(); TagsPathScope pathScope(tagsPath_, tagName); - bool tagMatched = checkIfFoundTag(ctx); - if (field >= 0) { - if (tagType == TAG_ARRAY) { - const int count = ctx.rdser.GetVarUint(); - if (!tagMatched || !ctx.fieldUpdated) { - auto &lastTag = tagsPath_.back(); - for (int i = 0; i < count; ++i) { - lastTag.SetIndex(i); - const bool isLastItem = (i + 1 == count); - tagMatched = checkIfFoundTag(ctx, isLastItem); - if (tagMatched && ctx.fieldUpdated) { - break; - } - } - } - - if (tagMatched && ctx.fieldUpdated) { - const auto resultTagType = determineUpdateTagType(ctx, field); - ctx.wrser.PutCTag(ctag{resultTagType, tagName, field}); + if (isIndexed(field)) { + writeCTag(tag, ctx); + return true; + } - if (resultTagType == TAG_ARRAY) { - if (ctx.updateArrayElements) { - ctx.wrser.PutVarUint(count); - } else { - ctx.wrser.PutVarUint(ctx.value.size()); - } - } - } else { - ctx.wrser.PutCTag(ctag{tagType, tagName, field}); - ctx.wrser.PutVarUint(count); - } - } else { - if (tagMatched) { - if (ctx.updateArrayElements) { - throw Error(errParams, "Unable to update scalar value by index"); - } - const auto resultTagType = determineUpdateTagType(ctx, field); - ctx.wrser.PutCTag(ctag{resultTagType, tagName, field}); + const bool tagMatched = checkIfFoundTag(ctx); + const auto resultTagType = tagMatched ? determineUpdateTagType(ctx, field) : tagType; + ctx.wrser.PutCTag(ctag{resultTagType, tagName, field}); - if (resultTagType == TAG_ARRAY) { - ctx.wrser.PutVarUint(ctx.value.size()); - } + if (tagMatched) { + if (ctx.updateArrayElements && tagType != TAG_ARRAY) { + throw Error(errParams, "Unable to update scalar value by index"); + } + if (resultTagType != TAG_NULL) { + if (resultTagType == TAG_ARRAY) { + setArray(ctx); + } else if (ctx.value.empty()) { + throw Error(errLogic, "Update value for field [%s] cannot be empty", tagsMatcher_.tag2name(tagName)); + } else if (ctx.value.size() == 1) { + const auto item = ctx.value.front(); + copyCJsonValue(item.Type().ToTagType(), item, ctx.wrser); } else { - ctx.wrser.PutCTag(ctag{tagType, tagName, field}); + throw Error(errParams, "Unexpected value to update"); } } - } else { - const auto resultTagType = tagMatched ? determineUpdateTagType(ctx, field) : tagType; - ctx.wrser.PutCTag(ctag{resultTagType, tagName, field}); - if (tagMatched) { - if (ctx.updateArrayElements && tagType != TAG_ARRAY) { - throw Error(errParams, "Unable to update scalar value by index"); - } - if (resultTagType != TAG_NULL) { - if (resultTagType == TAG_ARRAY) { - ctx.wrser.PutCArrayTag(carraytag{ctx.value.size(), kvType2Tag(ctx.value.ArrayType())}); - } else if (ctx.value.empty()) { - throw Error(errLogic, "Update value for field [%s] cannot be empty", tagsMatcher_.tag2name(tagName)); - } - for (size_t i = 0, size = ctx.value.size(); i < size; ++i) { - updateField(ctx, i); - } - } - skipCjsonTag(tag, ctx.rdser, &ctx.fieldsArrayOffsets); - } else if (tagType == TAG_OBJECT) { - TagsPathScope pathScope(ctx.currObjPath, tagName); - while (updateFieldInTuple(ctx)) { - } - } else if (tagType == TAG_ARRAY) { - const carraytag atag = ctx.rdser.GetCArrayTag(); - ctx.wrser.PutCArrayTag(atag); - const TagType atagType = atag.Type(); - const auto count = atag.Count(); - for (unsigned i = 0; i < count; i++) { - tagsPath_.back().SetIndex(i); - const bool isLastItem = (i + 1 == atag.Count()); - if (checkIfFoundTag(ctx, isLastItem)) { - if (ctx.value.IsArrayValue()) { - throw Error(errParams, "Unable to update non-indexed array's element with array-value"); - } - copyCJsonValue(atagType, ctx.value.front(), ctx.wrser); - skipCjsonTag(ctag{atagType}, ctx.rdser, &ctx.fieldsArrayOffsets); - } else { - switch (atagType) { - case TAG_OBJECT: { - TagsPathScope pathScope(ctx.currObjPath, tagName, i); - updateFieldInTuple(ctx); - break; - } - case TAG_VARINT: - case TAG_DOUBLE: - case TAG_STRING: - case TAG_ARRAY: - case TAG_NULL: - case TAG_BOOL: - case TAG_END: - case TAG_UUID: - copyCJsonValue(atagType, ctx.rdser, ctx.wrser); - break; - } - } - } - } else { - copyCJsonValue(tagType, ctx.rdser, ctx.wrser); + skipCjsonTag(tag, ctx.rdser, &ctx.fieldsArrayOffsets); + return true; + } + + if (tagType == TAG_OBJECT) { + TagsPathScope pathScopeObj(ctx.currObjPath, tagName); + while (updateFieldInTuple(ctx)) { } + return true; } + (tagType == TAG_ARRAY) ? copyArray(tagName, ctx) : copyCJsonValue(tagType, ctx.rdser, ctx.wrser); return true; } @@ -346,70 +434,77 @@ bool CJsonModifier::dropFieldInTuple(Context &ctx) { const TagType tagType = tag.Type(); ctx.wrser.PutCTag(ctag{tagType, tagName, field}); - if (field >= 0) { + if (isIndexed(field)) { if (tagType == TAG_ARRAY) { const auto count = ctx.rdser.GetVarUint(); ctx.wrser.PutVarUint(count); } - } else { - if (tagType == TAG_OBJECT) { - TagsPathScope pathScope(ctx.currObjPath, tagName); - while (dropFieldInTuple(ctx)) { - } - } else if (tagType == TAG_ARRAY) { - carraytag atag = ctx.rdser.GetCArrayTag(); - const TagType atagType = atag.Type(); - const int size = atag.Count(); - tagMatched = (fieldPath_.back().IsArrayNode() && tagsPath_ == fieldPath_); - if (tagMatched) { - atag = carraytag(fieldPath_.back().IsForAllItems() ? 0 : size - 1, atagType); - ctx.fieldUpdated = true; + return true; + } + + if (tagType == TAG_OBJECT) { + TagsPathScope pathScopeObj(ctx.currObjPath, tagName); + while (dropFieldInTuple(ctx)) { + } + return true; + } + + if (tagType == TAG_ARRAY) { + carraytag atag = ctx.rdser.GetCArrayTag(); + const TagType atagType = atag.Type(); + const auto size = int(atag.Count()); + tagMatched = (fieldPath_.back().IsArrayNode() && tagsPath_ == fieldPath_); + if (tagMatched) { + atag = carraytag(fieldPath_.back().IsForAllItems() ? 0 : size - 1, atagType); + ctx.fieldUpdated = true; + } + + ctx.wrser.PutCArrayTag(atag); + for (int i = 0; i < size; ++i) { + tagsPath_.back().SetIndex(i); + if (tagMatched && (i == fieldPath_.back().Index() || fieldPath_.back().IsForAllItems())) { + skipCjsonTag(ctag{atagType}, ctx.rdser, &ctx.fieldsArrayOffsets); + continue; } - ctx.wrser.PutCArrayTag(atag); - for (int i = 0; i < size; i++) { - tagsPath_.back().SetIndex(i); - if (tagMatched && (i == fieldPath_.back().Index() || fieldPath_.back().IsForAllItems())) { - skipCjsonTag(ctag{atagType}, ctx.rdser, &ctx.fieldsArrayOffsets); - } else { - switch (atagType) { - case TAG_OBJECT: { - TagsPathScope pathScope(ctx.currObjPath, tagName, i); - dropFieldInTuple(ctx); - break; - } - case TAG_VARINT: - case TAG_STRING: - case TAG_DOUBLE: - case TAG_BOOL: - case TAG_ARRAY: - case TAG_NULL: - case TAG_END: - case TAG_UUID: - copyCJsonValue(atagType, ctx.rdser, ctx.wrser); - break; - } + + switch (atagType) { + case TAG_OBJECT: { + TagsPathScope pathScopeObj(ctx.currObjPath, tagName, i); + dropFieldInTuple(ctx); + break; } + case TAG_VARINT: + case TAG_STRING: + case TAG_DOUBLE: + case TAG_BOOL: + case TAG_ARRAY: + case TAG_NULL: + case TAG_END: + case TAG_UUID: + copyCJsonValue(atagType, ctx.rdser, ctx.wrser); + break; } - } else { - copyCJsonValue(tagType, ctx.rdser, ctx.wrser); } + return true; } + copyCJsonValue(tagType, ctx.rdser, ctx.wrser); return true; } -void CJsonModifier::embedFieldValue(TagType type, int field, Context &ctx, size_t idx) { - if (field < 0) { - copyCJsonValue(type, ctx.rdser, ctx.wrser); - } else { +void CJsonModifier::embedFieldValue(TagType type, int field, Context &ctx, size_t idx) const { + if (isIndexed(field)) { assertrx(ctx.payload); - Variant v = ctx.payload->Get(field, ctx.fieldsArrayOffsets[field] + idx); + const Variant v = ctx.payload->Get(field, ctx.fieldsArrayOffsets[field] + idx); copyCJsonValue(type, v, ctx.wrser); + return; } + + copyCJsonValue(type, ctx.rdser, ctx.wrser); } bool CJsonModifier::buildCJSON(Context &ctx) { - const ctag tag = ctx.rdser.GetCTag(); + const auto tag = ctx.rdser.GetCTag(); if (tag == kCTagEnd) { if (needToInsertField(ctx)) insertField(ctx); ctx.wrser.PutCTag(kCTagEnd); @@ -417,29 +512,32 @@ bool CJsonModifier::buildCJSON(Context &ctx) { } TagType tagType = tag.Type(); const int tagName = tag.Name(); - const auto field = tag.Field(); TagsPathScope pathScope(tagsPath_, tagName); - const bool embeddedField = (field < 0); bool tagMatched = fieldPath_.Compare(tagsPath_); - if (!tagMatched) { - ctx.wrser.PutCTag(ctag{tagType, tagName}); - } else { + if (tagMatched) { tagType = TAG_OBJECT; + } else { + ctx.wrser.PutCTag(ctag{tagType, tagName}); } if (tagType == TAG_OBJECT) { if (tagMatched) { skipCjsonTag(tag, ctx.rdser, &ctx.fieldsArrayOffsets); updateObject(ctx, tagName); - } else { - TagsPathScope pathScope(ctx.currObjPath, tagName); - while (buildCJSON(ctx)) { - } + return true; + } + + TagsPathScope pathScopeObj(ctx.currObjPath, tagName); + while (buildCJSON(ctx)) { } - } else if (tagType == TAG_ARRAY) { - const carraytag atag{embeddedField ? ctx.rdser.GetCArrayTag() - : carraytag(ctx.rdser.GetVarUint(), kvType2Tag(pt_.Field(tag.Field()).Type()))}; + return true; + } + + const auto field = tag.Field(); + if (tagType == TAG_ARRAY) { + const carraytag atag{isIndexed(field) ? carraytag(ctx.rdser.GetVarUint(), pt_.Field(tag.Field()).Type().ToTagType()) + : ctx.rdser.GetCArrayTag()}; ctx.wrser.PutCArrayTag(atag); const auto arrSize = atag.Count(); for (size_t i = 0; i < arrSize; ++i) { @@ -448,36 +546,38 @@ bool CJsonModifier::buildCJSON(Context &ctx) { if (tagMatched) { updateObject(ctx, 0); skipCjsonTag(ctx.rdser.GetCTag(), ctx.rdser, &ctx.fieldsArrayOffsets); - } else { - switch (atag.Type()) { - case TAG_OBJECT: { - TagsPathScope pathScope(ctx.currObjPath, tagName); - buildCJSON(ctx); - break; - } - case TAG_VARINT: - case TAG_DOUBLE: - case TAG_STRING: - case TAG_BOOL: - case TAG_ARRAY: - case TAG_NULL: - case TAG_END: - case TAG_UUID: - embedFieldValue(atag.Type(), field, ctx, i); - break; + continue; + } + + switch (atag.Type()) { + case TAG_OBJECT: { + TagsPathScope pathScopeObj(ctx.currObjPath, tagName); + buildCJSON(ctx); + break; } + case TAG_VARINT: + case TAG_DOUBLE: + case TAG_STRING: + case TAG_BOOL: + case TAG_ARRAY: + case TAG_NULL: + case TAG_END: + case TAG_UUID: + embedFieldValue(atag.Type(), field, ctx, i); + break; } } - if (field >= 0) { + + if (isIndexed(field)) { ctx.fieldsArrayOffsets[field] += arrSize; } - } else { - embedFieldValue(tagType, field, ctx, 0); - if (field >= 0) { - ctx.fieldsArrayOffsets[field] += 1; - } + return true; } + embedFieldValue(tagType, field, ctx, 0); + if (isIndexed(field)) { + ctx.fieldsArrayOffsets[field] += 1; + } return true; } diff --git a/cpp_src/core/cjson/cjsonmodifier.h b/cpp_src/core/cjson/cjsonmodifier.h index 5e3c12c7b..baa804d3e 100644 --- a/cpp_src/core/cjson/cjsonmodifier.h +++ b/cpp_src/core/cjson/cjsonmodifier.h @@ -11,22 +11,29 @@ class TagsMatcher; class CJsonModifier { public: CJsonModifier(TagsMatcher &tagsMatcher, PayloadType pt) noexcept : pt_(std::move(pt)), tagsMatcher_(tagsMatcher) {} - void SetFieldValue(std::string_view tuple, IndexedTagsPath path, const VariantArray &v, WrSerializer &ser, const Payload &pl); - void SetObject(std::string_view tuple, IndexedTagsPath path, const VariantArray &v, WrSerializer &ser, const Payload &pl); - void RemoveField(std::string_view tuple, IndexedTagsPath fieldPath, WrSerializer &wrser); + void SetFieldValue(std::string_view tuple, const IndexedTagsPath &fieldPath, const VariantArray &val, WrSerializer &ser, + const Payload &pl); + void SetObject(std::string_view tuple, const IndexedTagsPath &fieldPath, const VariantArray &val, WrSerializer &ser, const Payload &pl); + void RemoveField(std::string_view tuple, const IndexedTagsPath &fieldPath, WrSerializer &wrser); private: class Context; + Context initState(std::string_view tuple, const IndexedTagsPath &fieldPath, const VariantArray &val, WrSerializer &ser, + const Payload *pl, FieldModifyMode mode); bool updateFieldInTuple(Context &ctx); bool dropFieldInTuple(Context &ctx); bool buildCJSON(Context &ctx); - bool needToInsertField(const Context &ctx); - void insertField(Context &ctx); - void embedFieldValue(TagType, int field, Context &ctx, size_t idx); - void updateObject(Context &ctx, int tagName); - void updateField(Context &ctx, size_t idx); - TagType determineUpdateTagType(const Context &ctx, int field); - bool checkIfFoundTag(Context &ctx, bool isLastItem = false); + [[nodiscard]] bool needToInsertField(const Context &ctx) const; + void insertField(Context &ctx) const; + void embedFieldValue(TagType, int field, Context &ctx, size_t idx) const; + void updateObject(Context &ctx, int tagName) const; + void setArray(Context &ctx) const; + void writeCTag(const ctag &tag, Context &ctx); + void updateArray(TagType atagType, uint32_t count, int tagName, Context &ctx); + void copyArray(int TagName, Context &ctx); + [[nodiscard]] TagType determineUpdateTagType(const Context &ctx, int field) const; + [[nodiscard]] bool checkIfFoundTag(Context &ctx, bool isLastItem = false) const; + [[nodiscard]] bool isIndexed(int field) const noexcept { return (field >= 0); } PayloadType pt_; IndexedTagsPath fieldPath_, tagsPath_; diff --git a/cpp_src/core/cjson/cjsontools.cc b/cpp_src/core/cjson/cjsontools.cc index 3c2002c7a..4b95f3761 100644 --- a/cpp_src/core/cjson/cjsontools.cc +++ b/cpp_src/core/cjson/cjsontools.cc @@ -4,17 +4,24 @@ namespace reindexer { -TagType kvType2Tag(KeyValueType kvType) noexcept { - return kvType.EvaluateOneOf([](OneOf) noexcept { return TAG_VARINT; }, - [](KeyValueType::Bool) noexcept { return TAG_BOOL; }, - [](KeyValueType::Double) noexcept { return TAG_DOUBLE; }, - [](KeyValueType::String) noexcept { return TAG_STRING; }, - [](OneOf) noexcept { return TAG_NULL; }, - [](KeyValueType::Uuid) noexcept { return TAG_UUID; }, - [](OneOf) noexcept -> TagType { std::abort(); }); +TagType arrayKvType2Tag(const VariantArray &values) { + if (values.empty()) { + return TAG_NULL; + } + + auto it = values.begin(); + const auto type = it->Type().ToTagType(); + + ++it; + for (auto end = values.end(); it != end; ++it) { + if (type != it->Type().ToTagType()) { + return TAG_OBJECT; // heterogeneously array detected + } + } + return type; } -void copyCJsonValue(TagType tagType, Variant value, WrSerializer &wrser) { +void copyCJsonValue(TagType tagType, const Variant &value, WrSerializer &wrser) { if (value.Type().Is()) return; switch (tagType) { case TAG_DOUBLE: @@ -39,9 +46,11 @@ void copyCJsonValue(TagType tagType, Variant value, WrSerializer &wrser) { break; case TAG_NULL: break; + case TAG_OBJECT: + wrser.PutVariant(value); + break; case TAG_ARRAY: case TAG_END: - case TAG_OBJECT: throw Error(errParseJson, "Unexpected cjson typeTag '%s' while parsing value", TagTypeToStr(tagType)); } } @@ -57,13 +66,23 @@ void putCJsonRef(TagType tagType, int tagName, int tagField, const VariantArray void putCJsonValue(TagType tagType, int tagName, const VariantArray &values, WrSerializer &wrser) { if (values.IsArrayValue()) { - const TagType elemType = kvType2Tag(values.ArrayType()); + const TagType elemType = arrayKvType2Tag(values); wrser.PutCTag(ctag{TAG_ARRAY, tagName}); wrser.PutCArrayTag(carraytag{values.size(), elemType}); - for (const Variant &value : values) copyCJsonValue(elemType, value, wrser); + if (elemType == TAG_OBJECT) { + for (const Variant &value : values) { + auto itemType = value.Type().ToTagType(); + wrser.PutCTag(ctag{itemType}); + copyCJsonValue(itemType, value, wrser); + } + } else { + for (const Variant &value : values) copyCJsonValue(elemType, value, wrser); + } } else if (values.size() == 1) { wrser.PutCTag(ctag{tagType, tagName}); copyCJsonValue(tagType, values.front(), wrser); + } else { + throw Error(errParams, "Unexpected value to update json value"); } } @@ -86,10 +105,13 @@ void copyCJsonValue(TagType tagType, Serializer &rdser, WrSerializer &wrser) { case TAG_UUID: wrser.PutUuid(rdser.GetUuid()); break; - case TAG_END: case TAG_OBJECT: + wrser.PutVariant(rdser.GetVariant()); + break; + case TAG_END: case TAG_ARRAY: - throw Error(errParseJson, "Unexpected cjson typeTag '%s' while parsing value", TagTypeToStr(tagType)); + default: + throw Error(errParseJson, "Unexpected cjson typeTag '%d' while parsing value", int(tagType)); } } @@ -136,7 +158,9 @@ void skipCjsonTag(ctag tag, Serializer &rdser, std::array } else if (fieldsArrayOffsets) { (*fieldsArrayOffsets)[field] += 1; } - } + } break; + default: + throw Error(errParseJson, "skipCjsonTag: unexpected ctag type value: %d", int(tag.Type())); } } @@ -155,7 +179,7 @@ void buildPayloadTuple(const PayloadIface &pl, const TagsMatcher *tagsMatcher continue; } - int tagName = tagsMatcher->name2tag(fieldType.JsonPaths()[0]); + const int tagName = tagsMatcher->name2tag(fieldType.JsonPaths()[0]); assertf(tagName != 0, "ns=%s, field=%s", pl.Type().Name(), fieldType.JsonPaths()[0]); if (fieldType.IsArray()) { diff --git a/cpp_src/core/cjson/cjsontools.h b/cpp_src/core/cjson/cjsontools.h index b99d39b3c..dddfbf2a5 100644 --- a/cpp_src/core/cjson/cjsontools.h +++ b/cpp_src/core/cjson/cjsontools.h @@ -8,11 +8,11 @@ template void buildPayloadTuple(const PayloadIface &pl, const TagsMatcher *tagsMatcher, WrSerializer &wrser); void copyCJsonValue(TagType tagType, Serializer &rdser, WrSerializer &wrser); -void copyCJsonValue(TagType tagType, Variant value, WrSerializer &wrser); +void copyCJsonValue(TagType tagType, const Variant &value, WrSerializer &wrser); void putCJsonRef(TagType tagType, int tagName, int tagField, const VariantArray &values, WrSerializer &wrser); void putCJsonValue(TagType tagType, int tagName, const VariantArray &values, WrSerializer &wrser); -[[nodiscard]] TagType kvType2Tag(KeyValueType kvType) noexcept; +[[nodiscard]] TagType arrayKvType2Tag(const VariantArray &values); void skipCjsonTag(ctag tag, Serializer &rdser, std::array *fieldsArrayOffsets = nullptr); [[nodiscard]] Variant cjsonValueToVariant(TagType tag, Serializer &rdser, KeyValueType dstType); diff --git a/cpp_src/core/cjson/ctag.h b/cpp_src/core/cjson/ctag.h index b591ad616..19567dd13 100644 --- a/cpp_src/core/cjson/ctag.h +++ b/cpp_src/core/cjson/ctag.h @@ -33,8 +33,8 @@ class ctag { static constexpr uint32_t kNameMask = (uint32_t(1) << kNameBits) - uint32_t(1); static constexpr int kNameMax = (1 << kNameBits) - 1; - constexpr explicit ctag(TagType tagType) noexcept : ctag{tagType, 0} {} - constexpr ctag(TagType tagType, int tagName, int tagField = -1) noexcept + RX_ALWAYS_INLINE constexpr explicit ctag(TagType tagType) noexcept : ctag{tagType, 0} {} + RX_ALWAYS_INLINE constexpr ctag(TagType tagType, int tagName, int tagField = -1) noexcept : tag_{(uint32_t(tagType) & kTypeMask) | (uint32_t(tagName) << kTypeBits) | (uint32_t(tagField + 1) << kFieldOffset) | ((uint32_t(tagType) & kInvertedTypeMask) << kType1Offset)} { assertrx(tagName >= 0); @@ -43,22 +43,28 @@ class ctag { assertrx(tagField + 1 < (1 << kFieldBits)); } - [[nodiscard]] constexpr TagType Type() const noexcept { return typeImpl(tag_); } - [[nodiscard]] constexpr int Name() const noexcept { return nameImpl(tag_); } - [[nodiscard]] constexpr int Field() const noexcept { return fieldImpl(tag_); } + [[nodiscard]] RX_ALWAYS_INLINE constexpr TagType Type() const noexcept { return typeImpl(tag_); } + [[nodiscard]] RX_ALWAYS_INLINE constexpr int Name() const noexcept { return nameImpl(tag_); } + [[nodiscard]] RX_ALWAYS_INLINE constexpr int Field() const noexcept { return fieldImpl(tag_); } - [[nodiscard]] constexpr bool operator==(ctag other) const noexcept { return tag_ == other.tag_; } - [[nodiscard]] constexpr bool operator!=(ctag other) const noexcept { return !operator==(other); } + [[nodiscard]] RX_ALWAYS_INLINE constexpr bool operator==(ctag other) const noexcept { return tag_ == other.tag_; } + [[nodiscard]] RX_ALWAYS_INLINE constexpr bool operator!=(ctag other) const noexcept { return !operator==(other); } private: - explicit constexpr ctag(uint32_t tag) noexcept : ctag{typeImpl(tag), nameImpl(tag), fieldImpl(tag)} { assertrx_dbg(tag == tag_); } - explicit constexpr ctag(uint64_t tag) noexcept : ctag{typeImpl(tag), nameImpl(tag), fieldImpl(tag)} { assertrx_dbg(tag == tag_); } - [[nodiscard]] constexpr static TagType typeImpl(uint32_t tag) noexcept { + RX_ALWAYS_INLINE explicit constexpr ctag(uint32_t tag) noexcept : ctag{typeImpl(tag), nameImpl(tag), fieldImpl(tag)} { + assertrx_dbg(tag == tag_); + } + RX_ALWAYS_INLINE explicit constexpr ctag(uint64_t tag) noexcept : ctag{typeImpl(tag), nameImpl(tag), fieldImpl(tag)} { + assertrx_dbg(tag == tag_); + } + [[nodiscard]] RX_ALWAYS_INLINE constexpr static TagType typeImpl(uint32_t tag) noexcept { return static_cast((tag & kTypeMask) | ((tag >> kType1Offset) & kInvertedTypeMask)); } - [[nodiscard]] constexpr static int nameImpl(uint32_t tag) noexcept { return (tag >> kTypeBits) & kNameMask; } - [[nodiscard]] constexpr static int fieldImpl(uint32_t tag) noexcept { return int((tag >> kFieldOffset) & kFieldMask) - 1; } - [[nodiscard]] constexpr uint32_t asNumber() const noexcept { return tag_; } + [[nodiscard]] RX_ALWAYS_INLINE constexpr static int nameImpl(uint32_t tag) noexcept { return (tag >> kTypeBits) & kNameMask; } + [[nodiscard]] RX_ALWAYS_INLINE constexpr static int fieldImpl(uint32_t tag) noexcept { + return int((tag >> kFieldOffset) & kFieldMask) - 1; + } + [[nodiscard]] RX_ALWAYS_INLINE constexpr uint32_t asNumber() const noexcept { return tag_; } uint32_t tag_; }; @@ -79,22 +85,23 @@ class carraytag { static constexpr uint32_t kTypeMask = (uint32_t(1) << kTypeBits) - uint32_t(1); public: - constexpr carraytag(uint32_t count, TagType tag) noexcept : atag_(count | (uint32_t(tag) << kCountBits)) { + RX_ALWAYS_INLINE constexpr carraytag(uint32_t count, TagType tag) noexcept : atag_(count | (uint32_t(tag) << kCountBits)) { assertrx(count < (uint32_t(1) << kCountBits)); } - [[nodiscard]] constexpr TagType Type() const noexcept { return typeImpl(atag_); } - [[nodiscard]] constexpr uint32_t Count() const noexcept { return countImpl(atag_); } + [[nodiscard]] RX_ALWAYS_INLINE constexpr TagType Type() const noexcept { return typeImpl(atag_); } + [[nodiscard]] RX_ALWAYS_INLINE constexpr uint32_t Count() const noexcept { return countImpl(atag_); } - [[nodiscard]] constexpr bool operator==(carraytag other) const noexcept { return atag_ == other.atag_; } - [[nodiscard]] constexpr bool operator!=(carraytag other) const noexcept { return !operator==(other); } + [[nodiscard]] RX_ALWAYS_INLINE constexpr bool operator==(carraytag other) const noexcept { return atag_ == other.atag_; } + [[nodiscard]] RX_ALWAYS_INLINE constexpr bool operator!=(carraytag other) const noexcept { return !operator==(other); } private: - explicit constexpr carraytag(uint32_t atag) noexcept : carraytag{countImpl(atag), typeImpl(atag)} { assertrx_dbg(atag == atag_); } - [[nodiscard]] constexpr uint32_t asNumber() const noexcept { return atag_; } - [[nodiscard]] static constexpr TagType typeImpl(uint32_t atag) noexcept { - return static_cast((atag >> kCountBits) & kTypeMask); + RX_ALWAYS_INLINE explicit constexpr carraytag(uint32_t atag) noexcept : carraytag{countImpl(atag), typeImpl(atag)} { assertrx_dbg(atag == atag_); } + [[nodiscard]] RX_ALWAYS_INLINE constexpr uint32_t asNumber() const noexcept { return atag_; } + [[nodiscard]] RX_ALWAYS_INLINE static constexpr TagType typeImpl(uint32_t atag) noexcept { + assertrx_dbg(((atag >> kCountBits) & kTypeMask) <= kMaxTagType); + return static_cast((atag >> kCountBits) & kTypeMask); // NOLINT(*EnumCastOutOfRange) } - [[nodiscard]] static constexpr uint32_t countImpl(uint32_t atag) noexcept { return atag & kCountMask; } + [[nodiscard]] RX_ALWAYS_INLINE static constexpr uint32_t countImpl(uint32_t atag) noexcept { return atag & kCountMask; } uint32_t atag_; }; diff --git a/cpp_src/core/cjson/defaultvaluecoder.cc b/cpp_src/core/cjson/defaultvaluecoder.cc new file mode 100644 index 000000000..6c6b5eb96 --- /dev/null +++ b/cpp_src/core/cjson/defaultvaluecoder.cc @@ -0,0 +1,161 @@ +#include "defaultvaluecoder.h" + +namespace reindexer { + +DefaultValueCoder::DefaultValueCoder(std::string_view ns, const PayloadFieldType &fld, std::vector &&tps, int16_t fieldIdx) + : ns_(ns), + field_(fld.Name()), + tags_(std::move(tps)), + fieldIdx_(fieldIdx), + type_(fld.Type().ToTagType()), + array_(fld.IsArray()), + basePath_(&tags_.front()) {} + +bool DefaultValueCoder::Match(int field) noexcept { + // non-nested field present in tuple + if ((field == fieldIdx_) && ready()) { + state_ = State::found; + } + return false; // returned result is always same +} + +bool DefaultValueCoder::Match(TagType tt, const TagsPath &tp) { + static const bool result = false; // returned result is always same + + // nothing to look for (start tuple global object) + if (tp.empty()) { + state_ = State::wait; + inArray_ = false; + arrField_ = 0; + return result; + } + + // found\recorded earlier + if ((state_ == State::found) || ((state_ == State::write) && !inArray_)) { + return result; + } + + // check if active array has been processed + const bool arrayTag = (tt == TAG_ARRAY); + if (inArray_) { + inArray_ = ((tt == TAG_OBJECT) || arrayTag) ? (tp.back() == arrField_) : (tp[tp.size() - 2] == arrField_); // -2 pre-last item + // recorded earlier - stop it + if (!inArray_ && (state_ == State::write)) { + return result; + } + } + + // try match nested field + if (tt == TAG_OBJECT) { + assertrx(state_ != State::found); + match(tp); + return result; + } + + // may be end element of adjacent nested field + if (arrayTag) { + inArray_ = (tp.front() == basePath_->front()); + arrField_ = tp.back(); + } + + // not nested + if (copyPos_ == 0) { + return result; + } + + // detect array insertion into array (not supported) + if (arrayTag && array_) { + state_ = State::found; // do nothing + } else if ((tp.front() == basePath_->front()) && (tp.size() > basePath_->size())) { + ++nestingLevel_; + } + + return result; +} + +void DefaultValueCoder::Serialize(WrSerializer &wrser) { + if (blocked()) { + return; // skip processing + } + + // skip nested levels + if ((basePath_->size() > 1) || (nestingLevel_ > 1)) { + assertrx(nestingLevel_ > 0); + --nestingLevel_; + + // new field - move to valid level + if (nestingLevel_ > copyPos_) { + return; + } + } + + write(wrser); + Reset(); + state_ = State::write; +} + +bool DefaultValueCoder::Reset() noexcept { + nestingLevel_ = 1; + copyPos_ = 0; + // NOTE: return true when updating tuple + return (state_ == State::write); +} + +void DefaultValueCoder::match(const TagsPath &tp) { + ++nestingLevel_; + + for (auto &path : tags_) { + if (path.front() != tp.front()) { + continue; + } + + copyPos_ = 1; + auto pathSize = path.size(); + auto sz = std::min(pathSize, tp.size()); + for (size_t idx = 1; idx < sz; ++idx) { + if (path[idx] != tp[idx]) { + break; + } + ++copyPos_; + + // we are trying to add field with non-nested paths, but an intersection was found in additional nested paths. + // Stop, throw an error + if (tags_.front().size() == 1) { + throw Error(errLogic, "Cannot add field with name '%s' to namespace '%s'. One of nested json paths is already in use", + field_, ns_); + } + } + state_ = State::match; + basePath_ = &path; + break; + } +} + +void DefaultValueCoder::write(WrSerializer &wrser) const { + int32_t nestedObjects = 0; + for (size_t idx = copyPos_, sz = basePath_->size(); idx < sz; ++idx) { + auto tagName = (*basePath_)[idx]; + // real index field in last tag + const bool finalTag = (idx == (sz - 1)); + if (finalTag) { + if (array_) { + wrser.PutCTag(ctag{TAG_ARRAY, tagName, fieldIdx_}); + wrser.PutVarUint(0); + } else { + wrser.PutCTag(ctag{type_, tagName, fieldIdx_}); + } + break; + } + + // start nested object + wrser.PutCTag(ctag{TAG_OBJECT, tagName}); + ++nestedObjects; + } + + // add end tags to all objects + while (nestedObjects-- > 0) { + wrser.PutCTag(kCTagEnd); + } +} + +} // namespace reindexer diff --git a/cpp_src/core/cjson/defaultvaluecoder.h b/cpp_src/core/cjson/defaultvaluecoder.h new file mode 100644 index 000000000..3cf9a5dac --- /dev/null +++ b/cpp_src/core/cjson/defaultvaluecoder.h @@ -0,0 +1,41 @@ +#pragma once + +#include "cjsondecoder.h" + +namespace reindexer { + +class DefaultValueCoder : public Recoder { +public: + DefaultValueCoder(std::string_view ns, const PayloadFieldType &fld, std::vector &&tps, int16_t fieldIdx); + RX_ALWAYS_INLINE TagType Type(TagType tt) noexcept override final { return tt; } + [[nodiscard]] bool Match(int f) noexcept override final; + [[nodiscard]] bool Match(TagType tt, const TagsPath &tp) override final; + RX_ALWAYS_INLINE void Recode(Serializer &, WrSerializer &) const noexcept override final { assertrx(false); } + RX_ALWAYS_INLINE void Recode(Serializer &, Payload &, int, WrSerializer &) noexcept override final { assertrx(false); } + void Serialize(WrSerializer &wrser) override final; + bool Reset() noexcept override final; + +private: + void match(const TagsPath &tp); + void write(WrSerializer &wrser) const; + [[nodiscard]] RX_ALWAYS_INLINE bool blocked() const noexcept { return ((state_ == State::found) || (state_ == State::write)); } + [[nodiscard]] RX_ALWAYS_INLINE bool ready() const noexcept { return ((state_ == State::wait) || (state_ == State::match)); } + +private: + const std::string ns_; + const std::string field_; + const std::vector tags_; + const int16_t fieldIdx_{0}; + const TagType type_; + const bool array_{false}; + + const TagsPath *basePath_{nullptr}; + enum class State { wait, found, match, write } state_{State::wait}; + uint32_t nestingLevel_{1}; + uint32_t copyPos_{0}; + + bool inArray_{false}; + int16_t arrField_{0}; +}; + +} // namespace reindexer diff --git a/cpp_src/core/cjson/fieldextractor.h b/cpp_src/core/cjson/fieldextractor.h index 43ac72d64..79247070d 100644 --- a/cpp_src/core/cjson/fieldextractor.h +++ b/cpp_src/core/cjson/fieldextractor.h @@ -93,16 +93,17 @@ class FieldsExtractor { params_->length = count; } } + const KeyValueType kvt{tagType}; if (ptype == PathType::WithIndex) { for (int i = 0; i < count; ++i) { - auto value = ser.GetRawVariant(KeyValueType(tagType)); + auto value = ser.GetRawVariant(kvt); if (i == pathNode.Index()) { put(0, std::move(value)); } } } else { for (int i = 0; i < count; ++i) { - put(0, ser.GetRawVariant(KeyValueType(tagType))); + put(0, ser.GetRawVariant(kvt)); } } if (expectedPathDepth_ <= 0) { diff --git a/cpp_src/core/cjson/jsonbuilder.h b/cpp_src/core/cjson/jsonbuilder.h index a8be4347c..813a2fd42 100644 --- a/cpp_src/core/cjson/jsonbuilder.h +++ b/cpp_src/core/cjson/jsonbuilder.h @@ -31,12 +31,12 @@ class JsonBuilder { JsonBuilder Array(int tagName, int size = KUnknownFieldSize) { return Array(getNameByTag(tagName), size); } template - void Array(int tagName, span data, int /*offset*/ = 0) { + void Array(int tagName, span data, int /*offset*/ = 0) { JsonBuilder node = Array(tagName); for (const auto &d : data) node.Put({}, d); } template - void Array(std::string_view n, span data, int /*offset*/ = 0) { + void Array(std::string_view n, span data, int /*offset*/ = 0) { JsonBuilder node = Array(n); for (const auto &d : data) node.Put({}, d); } @@ -48,7 +48,8 @@ class JsonBuilder { void Array(int tagName, Serializer &ser, TagType tagType, int count) { JsonBuilder node = Array(tagName); - while (count--) node.Put({}, ser.GetRawVariant(KeyValueType{tagType})); + const KeyValueType kvt{tagType}; + while (count--) node.Put({}, ser.GetRawVariant(kvt)); } JsonBuilder &Put(std::string_view name, const Variant &arg, int offset = 0); diff --git a/cpp_src/core/cjson/jsondecoder.cc b/cpp_src/core/cjson/jsondecoder.cc index d10e82539..aa153a828 100644 --- a/cpp_src/core/cjson/jsondecoder.cc +++ b/cpp_src/core/cjson/jsondecoder.cc @@ -70,10 +70,12 @@ void JsonDecoder::decodeJsonObject(Payload &pl, CJsonBuilder &builder, const gas case gason::JSON_FALSE: { validateNonArrayFieldRestrictions(objectScalarIndexes_, pl, f, field, isInArray(), "json"); objectScalarIndexes_.set(field); - Variant v = jsonValue2Variant(elem.value, f.Type(), f.Name()); - builder.Ref(tagName, v, field); - pl.Set(field, std::move(v), true); + Variant value = jsonValue2Variant(elem.value, f.Type(), f.Name()); + builder.Ref(tagName, value, field); + pl.Set(field, std::move(value), true); } break; + default: + throw Error(errLogic, "Unexpected '%d' tag", elem.value.getTag()); } } else { // objectScalarIndexes_.set(field); - do not change objectScalarIndexes_ value for the filtered out fields @@ -132,6 +134,8 @@ void JsonDecoder::decodeJson(Payload *pl, CJsonBuilder &builder, const gason::Js } break; } + default: + throw Error(errLogic, "Unexpected '%d' tag", jsonTag); } } diff --git a/cpp_src/core/cjson/msgpackbuilder.cc b/cpp_src/core/cjson/msgpackbuilder.cc index 32338967e..f50ba39eb 100644 --- a/cpp_src/core/cjson/msgpackbuilder.cc +++ b/cpp_src/core/cjson/msgpackbuilder.cc @@ -152,7 +152,7 @@ void MsgPackBuilder::appendJsonObject(std::string_view name, const gason::JsonNo break; } default: - throw(Error(errLogic, "Unexpected json tag: %d", obj.value.getTag())); + throw(Error(errLogic, "Unexpected json tag: %d", int(obj.value.getTag()))); } } diff --git a/cpp_src/core/cjson/protobufbuilder.h b/cpp_src/core/cjson/protobufbuilder.h index 0a75fa5db..22ac1e7c0 100644 --- a/cpp_src/core/cjson/protobufbuilder.h +++ b/cpp_src/core/cjson/protobufbuilder.h @@ -70,7 +70,7 @@ class ProtobufBuilder { template ::value || std::is_floating_point::value || std::is_same::value>::type* = nullptr> - void Array(int fieldIdx, span data, int /*offset*/ = 0) { + void Array(int fieldIdx, span data, int /*offset*/ = 0) { auto array = ArrayPacked(fieldIdx); for (const T& item : data) { array.put(0, item); @@ -78,13 +78,13 @@ class ProtobufBuilder { } template ::value>::type* = nullptr> - void Array(int fieldIdx, span data, int /*offset*/ = 0) { + void Array(int fieldIdx, span data, int /*offset*/ = 0) { auto array = ArrayNotPacked(fieldIdx); for (const T& item : data) { array.put(fieldIdx, std::string_view(item)); } } - void Array(int fieldIdx, span data, int /*offset*/ = 0) { + void Array(int fieldIdx, span data, int /*offset*/ = 0) { auto array = ArrayNotPacked(fieldIdx); for (Uuid item : data) { array.put(fieldIdx, item); diff --git a/cpp_src/core/cjson/readme.md b/cpp_src/core/cjson/readme.md index 5e152eb85..6cd159583 100644 --- a/cpp_src/core/cjson/readme.md +++ b/cpp_src/core/cjson/readme.md @@ -1,46 +1,53 @@ -`CJSON` (Compact JSON) is internal reindexer binary format for transparing represent JSON data. +`CJSON` (Compact JSON) is binary internal reindexer format for transparently representing JSON data. Each field of CJSON is encoded to `ctag` - varuint, which encodes type and name of field, and `data` binary representation of field data in format dependent of type. + ## Ctag format -| Bits | Field | Description | -|------|-------------|-------------------------------------------------------------------------| -| 3 | TypeTag | Type of field. One of TAG_XXX | -| 12 | NameIndex | Index of field's name in names dictionary. 0 - empty name | -| 6 | FieldIndex | Field index reference in internal reindexer payload. 0 - no reference. | +| Bits | Field | Description | +|------|------------|----------------------------------------------------------------------------------------------------------| +| 3 | TypeTag0 | Type of field. One of TAG_XXX | +| 12 | NameIndex | Index of field's name in names dictionary. 0 - empty name | +| 10 | FieldIndex | Field index reference in internal reindexer payload. 0 - no reference. | +| 4 | Reserved | Reserved for future use. | +| 3 | TypeTag1 | Additional high-order bits for the field type. Together with TypeTag0 they define the actual data type. | + ## Ctag type tag -| Name | Value | Description | -|------------|-------|---------------------------------| -| TAG_VARINT | 0 | Data is number in varint format | -| TAG_DOUBLE | 1 | Data is number in double format | -| TAG_STRING | 2 | Data is string with varint length | -| TAG_ARRAY | 3 | Data is array of elements | -| TAG_BOOL | 4 | Data is bool | -| TAG_NULL | 5 | Null | -| TAG_OBJECT | 6 | Data is object | -| TAG_END | 7 | End of object | +| Name | Value | Description | +|------------|-------|--------------------------------------------------| +| TAG_VARINT | 0 | Data is number in varint format | +| TAG_DOUBLE | 1 | Data is number in double format | +| TAG_STRING | 2 | Data is string with varint length | +| TAG_BOOL | 3 | Data is bool | +| TAG_NULL | 4 | Null | +| TAG_ARRAY | 5 | Data is array of elements | +| TAG_OBJECT | 6 | Data is object | +| TAG_END | 7 | End of object | +| TAG_UUID | 8 | Data in UUID format. High bit stored in TypeTag1 | ## Arrays Arrays can be stored in 2 different ways: -- homogeneous array, with all elements of same type -- mixed array, with elements of various types +- homogeneous array, with all elements of same type (Format: TAG_ARRAY + Atag(TAG_{item} + COUNT), every item write in item format) +- heterogeneous array, with elements of various types (Format: TAG_ARRAY + Atag(TAG_OBJECT + COUNT), every item write in item format with Ctag(TAG_{item} + NameIndex=0 + FieldIndex=0)) + ### Atag - array tag format -Atag is 4 byte int, which encodes type and count elements in array +Atag is 4 byte int, which encodes type and count elements in array (TTTTTTTTNNNNNNNNNNNNNNNNNNNNNNNN) + +| Bits | Field | Description | +|------|---------|---------------------------------------------------------------------------------------------------------| +| 6 | TypeTag | Type of array's elements. If TAG_OBJECT, than array is mixed, and each element contains individual ctag | +| 24 | Count | Count of elements in array | -| Bits | Field | Description | -|------|-------------|-------------------------------------------------------------------------| -| 24 | Count | Count of elements in array | -| 3 | TypeTag | Type of array's elements. If TAG_OBJECT, than array is mixed, and each element contains individual ctag| ## Record format @@ -67,11 +74,11 @@ record := ## CJSON pack format -| # | Field | Description | -|---|----------------------------|----------------------------------------------------------------------------| +| # | Field | Description | +|---|----------------------|----------------------------------------------------------------------------| | 1 | Offset to names dict | Offset to names dictionary. 0 if there are no new names dictionary in pack | -| 2 | Records | Tree of records. Begins with TAG_OBJECT, end TAG_END | -| 3 | Names dictionary | Dictionary of field names | +| 2 | Records | Tree of records. Begins with TAG_OBJECT, end TAG_END | +| 3 | Names dictionary | Dictionary of field names | ``` names_dictionary := @@ -82,6 +89,7 @@ names_dictionary := ] ``` + ## Example of CJSON ```json @@ -104,3 +112,48 @@ names_dictionary := (TAG_END) 07 (TAG_END) 07 ``` + + +### Array representations + +Thus, a heterogeneous array with two elements: the first string is "hello", the second boolean value is "true", can be encoded as follows: +```json +{ + "test": ["hi",true] +} +``` +``` +(TAG_ARRAY, field index) (TAG_OBJECT, array len) (TAG_STRING, string len, char array) (TAG_BOOL, value) +``` +``` +\065\002\000\000\006\002\002hi\003\001\a +``` +| Value | Descripton | +------------------|--------------------------------| +| \065 | Ctag(TAG_ARRAY) | +| \002\000\000\006 | Atag(2 item TAG_OBJECT) | +| \002 | Ctag(TAG_STRING) | +| \002hi | Item string, 2 character, "hi" | +| \003 | Ctag(TAG_BOOL) | +| \001 | Item boolean, 'true' | + + +Homogeneous array + +```json +{ + "test": ["hi","bro"] +} +``` +``` +(TAG_ARRAY, field index) (TAG_STRING, array len) (string len, char array) (string len, char array) +``` +``` +\065\002\000\000\002\002hi\003bro\a +``` +| Value | Descripton | +-------------------|---------------------------------| +| \065 | Ctag(TAG_ARRAY) | +| \002\000\000\002 | Atag(2 item TAG_STRING) | +| \002hi | Item string, 2 character, "hi" | +| \003bro | Item string, 3 character, "bro" | diff --git a/cpp_src/core/cjson/tagsmatcher.h b/cpp_src/core/cjson/tagsmatcher.h index e50faaf98..412da11f7 100644 --- a/cpp_src/core/cjson/tagsmatcher.h +++ b/cpp_src/core/cjson/tagsmatcher.h @@ -32,15 +32,15 @@ class TagsMatcher { auto res = path2tag(jsonPath); return res.empty() && canAdd ? impl_.clone()->path2tag(jsonPath, canAdd, updated_) : res; } - IndexedTagsPath path2indexedtag(std::string_view jsonPath, const IndexExpressionEvaluator& ev) const { - IndexedTagsPath tagsPath = impl_->path2indexedtag(jsonPath, ev); + IndexedTagsPath path2indexedtag(std::string_view jsonPath) const { + IndexedTagsPath tagsPath = impl_->path2indexedtag(jsonPath); assertrx(!updated_); return tagsPath; } - IndexedTagsPath path2indexedtag(std::string_view jsonPath, const IndexExpressionEvaluator& ev, bool canAdd) { + IndexedTagsPath path2indexedtag(std::string_view jsonPath, bool canAdd) { if (jsonPath.empty()) return IndexedTagsPath(); - auto res = impl_->path2indexedtag(jsonPath, ev); - return res.empty() && canAdd ? impl_.clone()->path2indexedtag(jsonPath, ev, canAdd, updated_) : res; + auto res = impl_->path2indexedtag(jsonPath); + return res.empty() && canAdd ? impl_.clone()->path2indexedtag(jsonPath, canAdd, updated_) : res; } int version() const noexcept { return impl_->version(); } size_t size() const noexcept { return impl_->size(); } @@ -60,12 +60,16 @@ class TagsMatcher { void setUpdated() noexcept { updated_ = true; } bool try_merge(const TagsMatcher& tm) { + if (impl_->contains(*tm.impl_)) { + return true; + } auto tmp = impl_; - if (!tmp.clone()->merge(*tm.impl_.get())) { + bool updated = false; + if (!tmp.clone()->merge(*tm.impl_, updated)) { return false; } impl_ = tmp; - updated_ = true; + updated_ = updated_ || updated; return true; } void add_names_from(const TagsMatcher& tm) { @@ -79,13 +83,13 @@ class TagsMatcher { void UpdatePayloadType(PayloadType payloadType, NeedChangeTmVersion changeVersion) { impl_.clone()->updatePayloadType(std::move(payloadType), updated_, changeVersion); } - static TagsMatcher CreateMergedTagsMatcher(PayloadType payloadType, const std::vector& tmList) { + static TagsMatcher CreateMergedTagsMatcher(const std::vector& tmList) { TagsMatcherImpl::TmListT implList; implList.reserve(tmList.size()); for (const auto& tm : tmList) { implList.emplace_back(tm.impl_.get()); } - TagsMatcher tm(make_intrusive>(std::move(payloadType), implList)); + TagsMatcher tm(make_intrusive>(implList)); return tm; } bool IsSubsetOf(const TagsMatcher& otm) const { return impl_->isSubsetOf(*otm.impl_); } diff --git a/cpp_src/core/cjson/tagsmatcherimpl.h b/cpp_src/core/cjson/tagsmatcherimpl.h index b3b0f9956..0e92c69d1 100644 --- a/cpp_src/core/cjson/tagsmatcherimpl.h +++ b/cpp_src/core/cjson/tagsmatcherimpl.h @@ -22,12 +22,13 @@ class TagsMatcherImpl { using TmListT = h_vector; TagsMatcherImpl() : version_(0), stateToken_(tools::RandomGenerator::gets32()) {} - TagsMatcherImpl(PayloadType payloadType, int32_t stateToken = tools::RandomGenerator::gets32()) - : payloadType_(payloadType), version_(0), stateToken_(stateToken) {} - TagsMatcherImpl(PayloadType payloadType, const TmListT &tmList) - : payloadType_(payloadType), version_(0), stateToken_(tools::RandomGenerator::gets32()) { - createMergedTagsMatcher(tmList); + TagsMatcherImpl(PayloadType &&payloadType, int32_t stateToken = tools::RandomGenerator::gets32()) + : version_(0), stateToken_(stateToken) { + bool updated = false; + updatePayloadType(std::move(payloadType), updated, NeedChangeTmVersion::No); + (void)updated; // No update check required } + TagsMatcherImpl(const TmListT &tmList) : version_(0), stateToken_(tools::RandomGenerator::gets32()) { createMergedTagsMatcher(tmList); } ~TagsMatcherImpl() = default; TagsPath path2tag(std::string_view jsonPath) const { @@ -55,12 +56,12 @@ class TagsMatcherImpl { return fieldTags; } - IndexedTagsPath path2indexedtag(std::string_view jsonPath, const IndexExpressionEvaluator &ev) const { + IndexedTagsPath path2indexedtag(std::string_view jsonPath) const { bool updated = false; - return const_cast(this)->path2indexedtag(jsonPath, ev, false, updated); + return const_cast(this)->path2indexedtag(jsonPath, false, updated); } - IndexedTagsPath path2indexedtag(std::string_view jsonPath, const IndexExpressionEvaluator &ev, bool canAdd, bool &updated) { + IndexedTagsPath path2indexedtag(std::string_view jsonPath, bool canAdd, bool &updated) { using namespace std::string_view_literals; IndexedTagsPath fieldTags; for (size_t pos = 0, lastPos = 0; pos != jsonPath.length(); lastPos = pos + 1) { @@ -86,22 +87,7 @@ class TagsMatcherImpl { } else { auto index = try_stoi(content); if (!index) { - if (ev) { - VariantArray values = ev(content); - if (values.size() != 1) { - throw Error(errParams, "Index expression_ has wrong syntax: '%s'", content); - } - values.front().Type().EvaluateOneOf( - [](OneOf) noexcept {}, - [&](OneOf) { - throw Error(errParams, "Wrong type of index: '%s'", content); - }); - node.SetExpression(content); - index = values.front().As(); - } else { - throw Error(errParams, "Can't convert '%s' to number", content); - } + throw Error(errParams, "Can't convert '%s' to number", content); } if (index < 0) { throw Error(errLogic, "Array index value cannot be negative"); @@ -174,8 +160,24 @@ class TagsMatcherImpl { } } void updatePayloadType(PayloadType payloadType, bool &updated, NeedChangeTmVersion changeVersion) { - updated = true; - payloadType_ = std::move(payloadType); + if (!payloadType && !payloadType_) { + return; + } + std::swap(payloadType_, payloadType); + bool newType = false; + buildTagsCache(newType); + newType = newType || bool(payloadType) != bool(payloadType_) || (payloadType_.NumFields() != payloadType.NumFields()); + if (!newType) { + for (int field = 1, fields = payloadType_.NumFields(); field < fields; ++field) { + auto &lf = payloadType_.Field(field); + auto &rf = payloadType.Field(field); + if (!lf.Type().IsSame(rf.Type()) || lf.IsArray() != rf.IsArray() || lf.JsonPaths() != rf.JsonPaths()) { + newType = true; + break; + } + } + } + updated = updated || newType; switch (changeVersion) { case NeedChangeTmVersion::Increment: ++version_; @@ -186,7 +188,6 @@ class TagsMatcherImpl { case NeedChangeTmVersion::No: break; } - buildTagsCache(updated); } void serialize(WrSerializer &ser) const { @@ -212,33 +213,37 @@ class TagsMatcherImpl { stateToken_ = stateToken; } - bool merge(const TagsMatcherImpl &tm) { - auto sz = tm.names2tags_.size(); - auto oldSz = size(); - - if (tags2names_.size() < sz) { - validateTagSize(sz); - tags2names_.resize(sz); - } - - for (auto it = tm.names2tags_.begin(), end = tm.names2tags_.end(); it != end; ++it) { - auto r = names2tags_.emplace(it->first, it->second); - if (!r.second && r.first->second != it->second) { - // name conflict - return false; + bool merge(const TagsMatcherImpl &tm, bool &updated) { + if (tm.contains(*this)) { + auto oldSz = size(); + auto newSz = tm.names2tags_.size(); + tags2names_.resize(newSz); + for (size_t i = oldSz; i < newSz; ++i) { + tags2names_[i] = tm.tags2names_[i]; + const auto r = names2tags_.emplace(tags2names_[i], i); + if (!r.second) { + // unexpected names conflict (this should never happen) + return false; + } } - if (r.second && it->second < int(oldSz)) { - // tag conflict - return false; + if (oldSz != newSz) { + updated = true; + if (version_ >= tm.version_) { + ++version_; + } else { + version_ = tm.version_; + } } - - tags2names_[it->second] = it->first; + return true; } - - version_ = std::max(version_, tm.version_) + 1; - - return true; + return contains(tm); + } + // Check if this tagsmatcher includes all of the tags from the other tagsmatcher + bool contains(const TagsMatcherImpl &tm) const noexcept { + return tags2names_.size() >= tm.tags2names_.size() && std::equal(tm.tags2names_.begin(), tm.tags2names_.end(), tags2names_.begin()); } + // Check if other tagsmatcher includes all of the tags from this tagsmatcher + bool isSubsetOf(const TagsMatcherImpl &tm) const noexcept { return tm.contains(*this); } bool add_names_from(const TagsMatcherImpl &tm) { bool modified = false; for (auto it = tm.names2tags_.begin(), end = tm.names2tags_.end(); it != end; ++it) { @@ -295,16 +300,6 @@ class TagsMatcherImpl { return res + "]"; } - // Check if other tagsmatcher includes all of the tags from this tagsmatcher - bool isSubsetOf(const TagsMatcherImpl &otm) const { - for (auto &pathP : names2tags_) { - const auto found = otm.names2tags_.find(pathP.first); - if (found == otm.names2tags_.end() || found->second != pathP.second) { - return false; - } - } - return true; - } protected: void createMergedTagsMatcher(const TmListT &tmList) { diff --git a/cpp_src/core/cjson/tagspath.h b/cpp_src/core/cjson/tagspath.h index 01faf64d0..390c2d9db 100644 --- a/cpp_src/core/cjson/tagspath.h +++ b/cpp_src/core/cjson/tagspath.h @@ -106,17 +106,28 @@ class IndexedTagsPathImpl : public h_vector { return true; } bool Compare(const TagsPath &obj) const noexcept { - if (obj.size() != this->size()) return false; - for (size_t i = 0; i < this->size(); ++i) { - if (this->operator[](i).NameTag() != obj[i]) return false; + const auto sz = this->size(); + if (obj.size() != sz) { + return false; + } + for (size_t i = 0; i < sz; ++i) { + if ((*this)[i].NameTag() != obj[i]) return false; + } + return true; + } + bool IsNestedOrEqualTo(const TagsPath &obj) const noexcept { + const auto sz = this->size(); + if (sz > obj.size()) { + return false; + } + for (size_t i = 0; i < sz; ++i) { + if ((*this)[i].NameTag() != obj[i]) return false; } return true; } }; using IndexedTagsPath = IndexedTagsPathImpl<6>; -using IndexExpressionEvaluator = std::function; - template class TagsPathScope { public: diff --git a/cpp_src/core/cjson/tagspathcache.h b/cpp_src/core/cjson/tagspathcache.h index dd94eef35..76ca80ca9 100644 --- a/cpp_src/core/cjson/tagspathcache.h +++ b/cpp_src/core/cjson/tagspathcache.h @@ -58,7 +58,8 @@ class TagsPathCache { } } - void clear() { entries_.clear(); } + void clear() noexcept { entries_.clear(); } + bool empty() const noexcept { return entries_.empty(); } protected: struct CacheEntry { diff --git a/cpp_src/core/cjson/uuid_recoders.h b/cpp_src/core/cjson/uuid_recoders.h index fbc0d60fe..c74af8e48 100644 --- a/cpp_src/core/cjson/uuid_recoders.h +++ b/cpp_src/core/cjson/uuid_recoders.h @@ -7,7 +7,7 @@ namespace reindexer { template class RecoderUuidToString : public Recoder { public: - RecoderUuidToString(TagsPath tp) noexcept : tagsPath_{std::move(tp)} {} + explicit RecoderUuidToString(TagsPath tp) noexcept : tagsPath_{std::move(tp)} {} [[nodiscard]] TagType Type([[maybe_unused]] TagType oldTagType) noexcept override final { if constexpr (Array) { assertrx(oldTagType == TAG_ARRAY); @@ -18,9 +18,11 @@ class RecoderUuidToString : public Recoder { } } void Recode(Serializer &, WrSerializer &) const override final; - void Recode(Serializer &, Payload &, int /*tagName*/, WrSerializer &) override final { assertrx(0); } - [[nodiscard]] bool Match(int) const noexcept override final { return false; } - [[nodiscard]] bool Match(const TagsPath &tp) const noexcept override final { return tagsPath_ == tp; } + void Recode(Serializer &, Payload &, int, WrSerializer &) override final { assertrx(false); } + [[nodiscard]] bool Match(int) noexcept override final { return false; } + [[nodiscard]] bool Match(TagType, const TagsPath &tp) noexcept override final { return tagsPath_ == tp; } + void Serialize(WrSerializer &) override final {} + bool Reset() override final { return false; } private: TagsPath tagsPath_; @@ -44,7 +46,7 @@ inline void RecoderUuidToString::Recode(Serializer &rdser, WrSerializer &w class RecoderStringToUuidArray : public Recoder { public: - RecoderStringToUuidArray(int f) noexcept : field_{f} {} + explicit RecoderStringToUuidArray(int f) noexcept : field_{f} {} [[nodiscard]] TagType Type(TagType oldTagType) override final { fromNotArrayField_ = oldTagType != TAG_ARRAY; if (fromNotArrayField_ && oldTagType != TAG_STRING) { @@ -52,9 +54,9 @@ class RecoderStringToUuidArray : public Recoder { } return TAG_ARRAY; } - [[nodiscard]] bool Match(int f) const noexcept override final { return f == field_; } - [[nodiscard]] bool Match(const TagsPath &) const noexcept override final { return false; } - void Recode(Serializer &, WrSerializer &) const override final { assertrx(0); } + [[nodiscard]] bool Match(int f) noexcept override final { return f == field_; } + [[nodiscard]] bool Match(TagType, const TagsPath &) noexcept override final { return false; } + void Recode(Serializer &, WrSerializer &) const override final { assertrx(false); } void Recode(Serializer &rdser, Payload &pl, int tagName, WrSerializer &wrser) override final { if (fromNotArrayField_) { pl.Set(field_, Variant{rdser.GetStrUuid()}, true); @@ -76,16 +78,18 @@ class RecoderStringToUuidArray : public Recoder { wrser.PutVarUint(count); } } + void Serialize(WrSerializer &) override final {} + bool Reset() override final { return false; } private: + const int field_{std::numeric_limits::max()}; VariantArray varBuf_; - int field_; bool fromNotArrayField_{false}; }; class RecoderStringToUuid : public Recoder { public: - RecoderStringToUuid(int f) noexcept : field_{f} {} + explicit RecoderStringToUuid(int f) noexcept : field_{f} {} [[nodiscard]] TagType Type(TagType oldTagType) override final { if (oldTagType == TAG_ARRAY) { throw Error(errLogic, "Cannot convert array field to not array UUID"); @@ -94,16 +98,18 @@ class RecoderStringToUuid : public Recoder { } return TAG_UUID; } - [[nodiscard]] bool Match(int f) const noexcept override final { return f == field_; } - [[nodiscard]] bool Match(const TagsPath &) const noexcept override final { return false; } - void Recode(Serializer &, WrSerializer &) const override final { assertrx(0); } + [[nodiscard]] bool Match(int f) noexcept override final { return f == field_; } + [[nodiscard]] bool Match(TagType, const TagsPath &) noexcept override final { return false; } + void Recode(Serializer &, WrSerializer &) const override final { assertrx(false); } void Recode(Serializer &rdser, Payload &pl, int tagName, WrSerializer &wrser) override final { pl.Set(field_, Variant{rdser.GetStrUuid()}, true); wrser.PutCTag(ctag{TAG_UUID, tagName, field_}); } + void Serialize(WrSerializer &) override final {} + bool Reset() override final { return false; } private: - int field_; + const int field_{std::numeric_limits::max()}; }; } // namespace reindexer diff --git a/cpp_src/core/clusterproxy.cc b/cpp_src/core/clusterproxy.cc index 2ec8e6222..4f98f6094 100644 --- a/cpp_src/core/clusterproxy.cc +++ b/cpp_src/core/clusterproxy.cc @@ -127,10 +127,14 @@ void ClusterProxy::resetLeader() { ClusterProxy::ClusterProxy(ReindexerConfig cfg, ActivityContainer &activities, ReindexerImpl::CallbackMap &&proxyCallbacks) : impl_(std::move(cfg), activities, addCallbacks(std::move(proxyCallbacks))), leaderId_(-1) { sId_.store(impl_.configProvider_.GetReplicationConfig().serverID, std::memory_order_release); - configHandlerId_ = - impl_.configProvider_.setHandler([this](ReplicationConfigData data) { sId_.store(data.serverID, std::memory_order_release); }); + replCfgHandlerID_ = impl_.configProvider_.setHandler( + [this](const ReplicationConfigData &data) { sId_.store(data.serverID, std::memory_order_release); }); +} +ClusterProxy::~ClusterProxy() { + if (replCfgHandlerID_.has_value()) { + impl_.configProvider_.unsetHandler(*replCfgHandlerID_); + } } -ClusterProxy::~ClusterProxy() { impl_.configProvider_.unsetHandler(configHandlerId_); } ReindexerImpl::CallbackMap ClusterProxy::addCallbacks(ReindexerImpl::CallbackMap &&callbackMap) const { // TODO: add callbacks for actions of ClusterProxy level diff --git a/cpp_src/core/clusterproxy.h b/cpp_src/core/clusterproxy.h index 5bee047e3..025e08393 100644 --- a/cpp_src/core/clusterproxy.h +++ b/cpp_src/core/clusterproxy.h @@ -173,7 +173,6 @@ class ClusterProxy { clusterProxyLog(LogTrace, "[%d proxy] ClusterProxy::Select query proxied", getServerIDRel()); return proxyCall(rdxDeadlineCtx, q.NsName(), action, q, qr); } - Error Commit(std::string_view nsName) { return impl_.Commit(nsName); } Item NewItem(std::string_view nsName, const RdxContext &ctx) { return impl_.NewItem(nsName, ctx); } Transaction NewTransaction(std::string_view nsName, const RdxContext &ctx) { @@ -291,10 +290,16 @@ class ClusterProxy { [[nodiscard]] Error ResetShardingConfigCandidate(int64_t sourceId, const RdxContext &ctx) noexcept; [[nodiscard]] Error RollbackShardingConfigCandidate(int64_t sourceId, const RdxContext &ctx) noexcept; - Error SubscribeUpdates(IUpdatesObserver *observer, const UpdatesFilters &filters, SubscriptionOpts opts) { + Error SubscribeUpdates(IEventsObserver &observer, EventSubscriberConfig &&cfg) { + return impl_.SubscribeUpdates(observer, std::move(cfg)); + } + Error UnsubscribeUpdates(IEventsObserver &observer) { return impl_.UnsubscribeUpdates(observer); } + + // REINDEX_WITH_V3_FOLLOWERS + Error SubscribeUpdates(IUpdatesObserverV3 *observer, const UpdatesFilters &filters, SubscriptionOpts opts) { return impl_.SubscribeUpdates(observer, filters, opts); } - Error UnsubscribeUpdates(IUpdatesObserver *observer) { return impl_.UnsubscribeUpdates(observer); } + Error UnsubscribeUpdates(IUpdatesObserverV3 *observer) { return impl_.UnsubscribeUpdates(observer); } // REINDEX_WITH_V3_FOLLOWERS int GetServerID() const noexcept { return sId_.load(std::memory_order_acquire); } @@ -379,7 +384,7 @@ class ClusterProxy { std::shared_ptr leader_; ConnectionsMap clusterConns_; std::atomic sId_; - int configHandlerId_; + std::optional replCfgHandlerID_; std::condition_variable processPingEvent_; std::mutex processPingEventMutex_; diff --git a/cpp_src/core/dbconfig.cc b/cpp_src/core/dbconfig.cc index d2491197f..9482e9737 100644 --- a/cpp_src/core/dbconfig.cc +++ b/cpp_src/core/dbconfig.cc @@ -185,7 +185,7 @@ void DBConfigProvider::setHandler(ConfigType cfgType, std::function hand handlers_[cfgType] = std::move(handler); } -int DBConfigProvider::setHandler(std::function handler) { +int DBConfigProvider::setHandler(std::function handler) { smart_lock lk(mtx_, true); replicationConfigDataHandlers_[++handlersCounter_] = std::move(handler); return handlersCounter_; @@ -223,50 +223,54 @@ bool DBConfigProvider::GetNamespaceConfig(std::string_view nsName, NamespaceConf Error ProfilingConfigData::FromJSON(const gason::JsonNode &v) { using namespace std::string_view_literals; std::string errorString; - tryReadOptionalJsonValue(&errorString, v, "queriesperfstats"sv, queriesPerfStats); - tryReadOptionalJsonValue(&errorString, v, "queries_threshold_us"sv, queriesThresholdUS); - tryReadOptionalJsonValue(&errorString, v, "perfstats"sv, perfStats); - tryReadOptionalJsonValue(&errorString, v, "memstats"sv, memStats); - tryReadOptionalJsonValue(&errorString, v, "activitystats"sv, activityStats); + auto err = tryReadOptionalJsonValue(&errorString, v, "queriesperfstats"sv, queriesPerfStats); + err = tryReadOptionalJsonValue(&errorString, v, "queries_threshold_us"sv, queriesThresholdUS); + err = tryReadOptionalJsonValue(&errorString, v, "perfstats"sv, perfStats); + err = tryReadOptionalJsonValue(&errorString, v, "memstats"sv, memStats); + err = tryReadOptionalJsonValue(&errorString, v, "activitystats"sv, activityStats); + (void)err; // ignored; Errors will be handled with errorString auto &longQueriesLogging = v["long_queries_logging"sv]; - if (!longQueriesLogging.empty()) { + if (longQueriesLogging.isObject()) { auto &select = longQueriesLogging["select"sv]; - if (!select.empty()) { + if (select.isObject()) { const auto p = longSelectLoggingParams.load(std::memory_order_relaxed); int32_t thresholdUs = p.thresholdUs; bool normalized = p.normalized; - tryReadOptionalJsonValue(&errorString, select, "threshold_us"sv, thresholdUs); - tryReadOptionalJsonValue(&errorString, select, "normalized"sv, normalized); + err = tryReadOptionalJsonValue(&errorString, select, "threshold_us"sv, thresholdUs); + err = tryReadOptionalJsonValue(&errorString, select, "normalized"sv, normalized); + (void)err; // ignored; Errors will be handled with errorString longSelectLoggingParams.store(LongQueriesLoggingParams(thresholdUs, normalized), std::memory_order_relaxed); } auto &updateDelete = longQueriesLogging["update_delete"sv]; - if (!updateDelete.empty()) { + if (updateDelete.isObject()) { const auto p = longUpdDelLoggingParams.load(std::memory_order_relaxed); int32_t thresholdUs = p.thresholdUs; bool normalized = p.normalized; - tryReadOptionalJsonValue(&errorString, updateDelete, "threshold_us"sv, thresholdUs); - tryReadOptionalJsonValue(&errorString, updateDelete, "normalized"sv, normalized); + err = tryReadOptionalJsonValue(&errorString, updateDelete, "threshold_us"sv, thresholdUs); + err = tryReadOptionalJsonValue(&errorString, updateDelete, "normalized"sv, normalized); + (void)err; // ignored; Errors will be handled with errorString longUpdDelLoggingParams.store(LongQueriesLoggingParams(thresholdUs, normalized), std::memory_order_relaxed); } auto &transaction = longQueriesLogging["transaction"sv]; - if (!transaction.empty()) { + if (transaction.isObject()) { const auto p = longTxLoggingParams.load(std::memory_order_relaxed); int32_t thresholdUs = p.thresholdUs; - tryReadOptionalJsonValue(&errorString, transaction, "threshold_us"sv, thresholdUs); + err = tryReadOptionalJsonValue(&errorString, transaction, "threshold_us"sv, thresholdUs); int32_t avgTxStepThresholdUs = p.avgTxStepThresholdUs; - tryReadOptionalJsonValue(&errorString, transaction, "avg_step_threshold_us"sv, avgTxStepThresholdUs); + err = tryReadOptionalJsonValue(&errorString, transaction, "avg_step_threshold_us"sv, avgTxStepThresholdUs); + (void)err; // ignored; Errors will be handled with errorString longTxLoggingParams.store(LongTxLoggingParams(thresholdUs, avgTxStepThresholdUs), std::memory_order_relaxed); } } if (!errorString.empty()) { return Error(errParseJson, "ProfilingConfigData: JSON parsing error: '%s'", errorString); - } else - return Error(); + } + return {}; } Error ReplicationConfigData::FromYAML(const std::string &yaml) { @@ -274,7 +278,7 @@ Error ReplicationConfigData::FromYAML(const std::string &yaml) { YAML::Node root = YAML::Load(yaml); clusterID = root["cluster_id"].as(clusterID); serverID = root["server_id"].as(serverID); - if (auto err = Validate()) { + if (auto err = Validate(); !err.ok()) { return Error(errParams, "ReplicationConfigData: YAML parsing error: '%s'", err.what()); } } catch (const YAML::Exception &ex) { @@ -298,8 +302,9 @@ Error ReplicationConfigData::FromJSON(std::string_view json) { Error ReplicationConfigData::FromJSON(const gason::JsonNode &root) { using namespace std::string_view_literals; std::string errorString; - tryReadOptionalJsonValue(&errorString, root, "cluster_id"sv, clusterID); - tryReadOptionalJsonValue(&errorString, root, "server_id"sv, serverID); + auto err = tryReadOptionalJsonValue(&errorString, root, "cluster_id"sv, clusterID); + err = tryReadOptionalJsonValue(&errorString, root, "server_id"sv, serverID); + (void)err; // ignored; Errors will be handled with errorString if (errorString.empty()) { if (auto err = Validate()) { @@ -364,8 +369,9 @@ std::ostream &operator<<(std::ostream &os, const ReplicationConfigData &data) { Error NamespaceConfigData::FromJSON(const gason::JsonNode &v) { using namespace std::string_view_literals; std::string errorString; - tryReadOptionalJsonValue(&errorString, v, "lazyload"sv, lazyLoad); - tryReadOptionalJsonValue(&errorString, v, "unload_idle_threshold"sv, noQueryIdleThreshold); + auto err = tryReadOptionalJsonValue(&errorString, v, "lazyload"sv, lazyLoad); + err = tryReadOptionalJsonValue(&errorString, v, "unload_idle_threshold"sv, noQueryIdleThreshold); + (void)err; // ignored; Errors will be handled with errorString std::string stringVal(logLevelToString(logLevel)); if (tryReadOptionalJsonValue(&errorString, v, "log_level"sv, stringVal).ok()) { @@ -386,41 +392,45 @@ Error NamespaceConfigData::FromJSON(const gason::JsonNode &v) { } } - tryReadOptionalJsonValue(&errorString, v, "start_copy_policy_tx_size"sv, startCopyPolicyTxSize); - tryReadOptionalJsonValue(&errorString, v, "copy_policy_multiplier"sv, copyPolicyMultiplier); - tryReadOptionalJsonValue(&errorString, v, "tx_size_to_always_copy"sv, txSizeToAlwaysCopy); - tryReadOptionalJsonValue(&errorString, v, "optimization_timeout_ms"sv, optimizationTimeout); - tryReadOptionalJsonValue(&errorString, v, "optimization_sort_workers"sv, optimizationSortWorkers); + err = tryReadOptionalJsonValue(&errorString, v, "start_copy_policy_tx_size"sv, startCopyPolicyTxSize); + err = tryReadOptionalJsonValue(&errorString, v, "copy_policy_multiplier"sv, copyPolicyMultiplier); + err = tryReadOptionalJsonValue(&errorString, v, "tx_size_to_always_copy"sv, txSizeToAlwaysCopy); + err = tryReadOptionalJsonValue(&errorString, v, "optimization_timeout_ms"sv, optimizationTimeout); + err = tryReadOptionalJsonValue(&errorString, v, "optimization_sort_workers"sv, optimizationSortWorkers); + (void)err; // ignored; Errors will be handled with errorString if (int64_t walSizeV = walSize; tryReadOptionalJsonValue(&errorString, v, "wal_size"sv, walSizeV, 0).ok()) { if (walSizeV > 0) { walSize = walSizeV; } } - tryReadOptionalJsonValue(&errorString, v, "min_preselect_size"sv, minPreselectSize, 0); - tryReadOptionalJsonValue(&errorString, v, "max_preselect_size"sv, maxPreselectSize, 0); - tryReadOptionalJsonValue(&errorString, v, "max_preselect_part"sv, maxPreselectPart, 0.0, 1.0); - tryReadOptionalJsonValue(&errorString, v, "index_updates_counting_mode"sv, idxUpdatesCountingMode); - tryReadOptionalJsonValue(&errorString, v, "sync_storage_flush_limit"sv, syncStorageFlushLimit, 0); + err = tryReadOptionalJsonValue(&errorString, v, "min_preselect_size"sv, minPreselectSize, 0); + err = tryReadOptionalJsonValue(&errorString, v, "max_preselect_size"sv, maxPreselectSize, 0); + err = tryReadOptionalJsonValue(&errorString, v, "max_preselect_part"sv, maxPreselectPart, 0.0, 1.0); + err = tryReadOptionalJsonValue(&errorString, v, "max_iterations_idset_preresult"sv, maxIterationsIdSetPreResult, 0); + err = tryReadOptionalJsonValue(&errorString, v, "index_updates_counting_mode"sv, idxUpdatesCountingMode); + err = tryReadOptionalJsonValue(&errorString, v, "sync_storage_flush_limit"sv, syncStorageFlushLimit, 0); + (void)err; // ignored; Errors will be handled with errorString auto cacheNode = v["cache"]; if (!cacheNode.empty()) { - tryReadOptionalJsonValue(&errorString, cacheNode, "index_idset_cache_size"sv, cacheConfig.idxIdsetCacheSize, 0); - tryReadOptionalJsonValue(&errorString, cacheNode, "index_idset_hits_to_cache"sv, cacheConfig.idxIdsetHitsToCache, 0); - tryReadOptionalJsonValue(&errorString, cacheNode, "ft_index_cache_size"sv, cacheConfig.ftIdxCacheSize, 0); - tryReadOptionalJsonValue(&errorString, cacheNode, "ft_index_hits_to_cache"sv, cacheConfig.ftIdxHitsToCache, 0); - tryReadOptionalJsonValue(&errorString, cacheNode, "joins_preselect_cache_size"sv, cacheConfig.joinCacheSize, 0); - tryReadOptionalJsonValue(&errorString, cacheNode, "joins_preselect_hit_to_cache"sv, cacheConfig.joinHitsToCache, 0); - tryReadOptionalJsonValue(&errorString, cacheNode, "query_count_cache_size"sv, cacheConfig.queryCountCacheSize, 0); - tryReadOptionalJsonValue(&errorString, cacheNode, "query_count_hit_to_cache"sv, cacheConfig.queryCountHitsToCache, 0); - tryReadOptionalJsonValue(&errorString, cacheNode, "index_idset_cache_size"sv, cacheConfig.idxIdsetCacheSize, 0); - tryReadOptionalJsonValue(&errorString, cacheNode, "index_idset_cache_size"sv, cacheConfig.idxIdsetCacheSize, 0); + err = tryReadOptionalJsonValue(&errorString, cacheNode, "index_idset_cache_size"sv, cacheConfig.idxIdsetCacheSize, 0); + err = tryReadOptionalJsonValue(&errorString, cacheNode, "index_idset_hits_to_cache"sv, cacheConfig.idxIdsetHitsToCache, 0); + err = tryReadOptionalJsonValue(&errorString, cacheNode, "ft_index_cache_size"sv, cacheConfig.ftIdxCacheSize, 0); + err = tryReadOptionalJsonValue(&errorString, cacheNode, "ft_index_hits_to_cache"sv, cacheConfig.ftIdxHitsToCache, 0); + err = tryReadOptionalJsonValue(&errorString, cacheNode, "joins_preselect_cache_size"sv, cacheConfig.joinCacheSize, 0); + err = tryReadOptionalJsonValue(&errorString, cacheNode, "joins_preselect_hit_to_cache"sv, cacheConfig.joinHitsToCache, 0); + err = tryReadOptionalJsonValue(&errorString, cacheNode, "query_count_cache_size"sv, cacheConfig.queryCountCacheSize, 0); + err = tryReadOptionalJsonValue(&errorString, cacheNode, "query_count_hit_to_cache"sv, cacheConfig.queryCountHitsToCache, 0); + err = tryReadOptionalJsonValue(&errorString, cacheNode, "index_idset_cache_size"sv, cacheConfig.idxIdsetCacheSize, 0); + err = tryReadOptionalJsonValue(&errorString, cacheNode, "index_idset_cache_size"sv, cacheConfig.idxIdsetCacheSize, 0); + (void)err; // ignored; Errors will be handled with errorString } if (!errorString.empty()) { return Error(errParseJson, "NamespaceConfigData: JSON parsing error: '%s'", errorString); } - return Error(); + return {}; } } // namespace reindexer diff --git a/cpp_src/core/dbconfig.h b/cpp_src/core/dbconfig.h index 7ed2d5804..db96f843d 100644 --- a/cpp_src/core/dbconfig.h +++ b/cpp_src/core/dbconfig.h @@ -106,17 +106,18 @@ struct NamespaceConfigData { LogLevel logLevel = LogNone; CacheMode cacheMode = CacheModeOff; StrictMode strictMode = StrictModeNames; - int startCopyPolicyTxSize = 10000; + int startCopyPolicyTxSize = 10'000; int copyPolicyMultiplier = 5; - int txSizeToAlwaysCopy = 100000; + int txSizeToAlwaysCopy = 100'000; int optimizationTimeout = 800; int optimizationSortWorkers = 4; - int64_t walSize = 4000000; - int64_t minPreselectSize = 1000; - int64_t maxPreselectSize = 1000; + int64_t walSize = 4'000'000; + int64_t minPreselectSize = 1'000; + int64_t maxPreselectSize = 1'000; double maxPreselectPart = 0.1; + int64_t maxIterationsIdSetPreResult = 20'000; bool idxUpdatesCountingMode = false; - int syncStorageFlushLimit = 20000; + int syncStorageFlushLimit = 20'000; NamespaceCacheConfigData cacheConfig; Error FromJSON(const gason::JsonNode &v); @@ -173,7 +174,7 @@ class DBConfigProvider { Error GetConfigParseErrors() const; void setHandler(ConfigType cfgType, std::function handler); - int setHandler(std::function handler); + int setHandler(std::function handler); void unsetHandler(int id); cluster::AsyncReplConfigData GetAsyncReplicationConfig(); @@ -202,7 +203,7 @@ class DBConfigProvider { Error replicationDataLoadResult_; fast_hash_map namespacesData_; std::array, kConfigTypesTotalCount> handlers_; - fast_hash_map> replicationConfigDataHandlers_; + fast_hash_map> replicationConfigDataHandlers_; int handlersCounter_ = 0; mutable shared_timed_mutex mtx_; }; diff --git a/cpp_src/core/defnsconfigs.h b/cpp_src/core/defnsconfigs.h index d73c190b6..ce97645f4 100644 --- a/cpp_src/core/defnsconfigs.h +++ b/cpp_src/core/defnsconfigs.h @@ -58,6 +58,7 @@ const std::vector kDefDBConfig = { "min_preselect_size":1000, "max_preselect_size":1000, "max_preselect_part":0.1, + "max_iterations_idset_preresult":20000, "index_updates_counting_mode":false, "sync_storage_flush_limit":20000, "cache":{ diff --git a/cpp_src/core/ft/areaholder.h b/cpp_src/core/ft/areaholder.h index ee52264d6..adbd06444 100644 --- a/cpp_src/core/ft/areaholder.h +++ b/cpp_src/core/ft/areaholder.h @@ -91,8 +91,8 @@ class AreaHolder { area.Commit(); } } - [[nodiscard]] bool AddWord(int pos, int filed, int32_t rank, int maxAreasInDoc) { - return InsertArea(Area{pos, pos + 1}, filed, rank, maxAreasInDoc); + [[nodiscard]] bool AddWord(int pos, int field, int32_t rank, int maxAreasInDoc) { + return InsertArea(Area{pos, pos + 1}, field, rank, maxAreasInDoc); } void UpdateRank(int32_t rank) noexcept { if (rank > maxTermRank_) { diff --git a/cpp_src/core/ft/config/baseftconfig.cc b/cpp_src/core/ft/config/baseftconfig.cc index 162b6cf1b..1a1046b91 100644 --- a/cpp_src/core/ft/config/baseftconfig.cc +++ b/cpp_src/core/ft/config/baseftconfig.cc @@ -15,7 +15,6 @@ void BaseFTConfig::parseBase(const gason::JsonNode &root) { enableTranslit = root["enable_translit"].As<>(enableTranslit); enableNumbersSearch = root["enable_numbers_search"].As<>(enableNumbersSearch); enableKbLayout = root["enable_kb_layout"].As<>(enableKbLayout); - enableWarmupOnNsCopy = root["enable_warmup_on_ns_copy"].As<>(enableWarmupOnNsCopy); mergeLimit = root["merge_limit"].As<>(mergeLimit, kMinMergeLimitValue, kMaxMergeLimitValue); logLevel = root["log_level"].As<>(logLevel, 0, 5); extraWordSymbols = root["extra_word_symbols"].As<>(extraWordSymbols); @@ -74,7 +73,6 @@ void BaseFTConfig::getJson(JsonBuilder &jsonBuilder) const { jsonBuilder.Put("enable_translit", enableTranslit); jsonBuilder.Put("enable_numbers_search", enableNumbersSearch); jsonBuilder.Put("enable_kb_layout", enableKbLayout); - jsonBuilder.Put("enable_warmup_on_ns_copy", enableWarmupOnNsCopy); jsonBuilder.Put("merge_limit", mergeLimit); jsonBuilder.Put("log_level", logLevel); jsonBuilder.Put("extra_word_symbols", extraWordSymbols); diff --git a/cpp_src/core/ft/config/baseftconfig.h b/cpp_src/core/ft/config/baseftconfig.h index bc91140bb..897208aa6 100644 --- a/cpp_src/core/ft/config/baseftconfig.h +++ b/cpp_src/core/ft/config/baseftconfig.h @@ -1,12 +1,10 @@ #pragma once -#include #include #include +#include "core/ft/stopwords/types.h" #include "core/ft/usingcontainer.h" #include "estl/fast_hash_map.h" -#include "estl/fast_hash_set.h" -#include "tools/stringstools.h" namespace gason { struct JsonNode; @@ -16,12 +14,6 @@ namespace reindexer { class JsonBuilder; -struct StopWord : std::string { - enum class Type { Stop, Morpheme }; - StopWord(std::string base, Type type = Type::Stop) noexcept : std::string(std::move(base)), type(type) {} - Type type; -}; - class BaseFTConfig { public: struct Synonym { @@ -41,9 +33,8 @@ class BaseFTConfig { bool enableTranslit = true; bool enableKbLayout = true; bool enableNumbersSearch = false; - bool enableWarmupOnNsCopy = false; - fast_hash_set stopWords; + StopWordsSetT stopWords; std::vector synonyms; int logLevel = 0; std::string extraWordSymbols = "-/+"; // word contains symbols (IsAlpa | IsDigit) {IsAlpa | IsDigit | IsExtra} diff --git a/cpp_src/core/ft/filters/translit.cc b/cpp_src/core/ft/filters/translit.cc index 7a882d309..114db5807 100644 --- a/cpp_src/core/ft/filters/translit.cc +++ b/cpp_src/core/ft/filters/translit.cc @@ -1,6 +1,7 @@ #include "translit.h" #include #include +#include "estl/span.h" namespace reindexer { @@ -12,6 +13,11 @@ Translit::Translit() { void Translit::GetVariants(const std::wstring &data, std::vector &result, int proc) { std::wstring strings[maxTranslitVariants]; Context ctx; + if (data.length()) { + for (int j = 0; j < maxTranslitVariants; ++j) { + strings[j].reserve(data.length()); + } + } for (size_t i = 0; i < data.length(); ++i) { wchar_t symbol = data[i]; @@ -43,18 +49,18 @@ void Translit::GetVariants(const std::wstring &data, std::vector & ctx.Clear(); } } - - std::wstring result_string; - + int64_t lastResultIdx = -1; for (int i = 0; i < maxTranslitVariants; ++i) { - auto &curent = strings[i]; - bool skip = false; + auto ¤t = strings[i]; for (int j = i + 1; j < maxTranslitVariants; ++j) { - if (curent == strings[j]) skip = true; + if (current == strings[j]) { + current.clear(); + break; + } } - if (!skip && curent != result_string && curent.length()) { - result_string = curent; - result.emplace_back(std::move(curent), proc); + if (current.length() && (lastResultIdx < 0 || current != result[lastResultIdx].pattern)) { + lastResultIdx = result.size(); + result.emplace_back(std::move(current), proc); } } } diff --git a/cpp_src/core/ft/ft_fast/dataholder.cc b/cpp_src/core/ft/ft_fast/dataholder.cc index 9ac65a56f..607dd675d 100644 --- a/cpp_src/core/ft/ft_fast/dataholder.cc +++ b/cpp_src/core/ft/ft_fast/dataholder.cc @@ -88,7 +88,7 @@ template size_t DataHolder::GetMemStat() { size_t res = IDataHolder::GetMemStat(); for (auto& w : words_) { - res += sizeof(w) + w.vids_.heap_size(); + res += sizeof(w) + w.vids.heap_size(); } return res; } @@ -110,13 +110,13 @@ void DataHolder::StartCommit(bool complte_updated) { words_.erase(words_.begin() + steps.back().wordOffset_, words_.end()); for (auto& word : words_) { - word.vids_.erase_back(word.cur_step_pos_); + word.vids.erase_back(word.cur_step_pos); } steps.back().clear(); } else { // if the last step is full, then create a new for (auto& word : words_) { - word.cur_step_pos_ = word.vids_.pos(word.vids_.end()); + word.cur_step_pos = word.vids.pos(word.vids.end()); } status_ = CreateNew; steps.emplace_back(CommitStep{}); diff --git a/cpp_src/core/ft/ft_fast/dataholder.h b/cpp_src/core/ft/ft_fast/dataholder.h index a7e76355e..e6aea4846 100644 --- a/cpp_src/core/ft/ft_fast/dataholder.h +++ b/cpp_src/core/ft/ft_fast/dataholder.h @@ -34,14 +34,30 @@ struct VDocEntry { template class PackedWordEntry { public: - IdCont vids_; // IdCont - std::vector or packed_vector + PackedWordEntry() noexcept = default; + PackedWordEntry(const PackedWordEntry&) = delete; + PackedWordEntry(PackedWordEntry&&) noexcept = default; + PackedWordEntry& operator=(const PackedWordEntry&) = delete; + PackedWordEntry& operator=(PackedWordEntry&&) noexcept = default; + + IdCont vids; // IdCont - std::vector or packed_vector // document offset, for the last step. // Necessary for correct rebuilding of the last step - size_t cur_step_pos_ = 0; + size_t cur_step_pos = 0; }; class WordEntry { public: - IdRelSet vids_; + WordEntry() noexcept = default; + WordEntry(const IdRelSet& _vids, bool _virtualWord) : vids(_vids), virtualWord(_virtualWord) {} + WordEntry(const WordEntry&) = delete; + WordEntry(WordEntry&&) noexcept = default; + WordEntry& operator=(const WordEntry&) = delete; + WordEntry& operator=(WordEntry&&) noexcept = default; + + // Explicit copy + WordEntry MakeCopy() const { return WordEntry(this->vids, this->virtualWord); } + + IdRelSet vids; bool virtualWord = false; }; enum ProcessStatus { FullRebuild, RecommitLast, CreateNew }; @@ -184,6 +200,7 @@ class DataHolder : public IDataHolder { void StartCommit(bool complte_updated) override final; void Clear() override final; std::vector>& GetWords() noexcept { return words_; } + const std::vector>& GetWords() const noexcept { return words_; } PackedWordEntry& GetWordById(WordIdType id) noexcept { assertrx(!id.IsEmpty()); assertrx(id.b.id < words_.size()); diff --git a/cpp_src/core/ft/ft_fast/dataprocessor.cc b/cpp_src/core/ft/ft_fast/dataprocessor.cc index 1f52275ae..a1373be73 100644 --- a/cpp_src/core/ft/ft_fast/dataprocessor.cc +++ b/cpp_src/core/ft/ft_fast/dataprocessor.cc @@ -1,6 +1,5 @@ #include "dataprocessor.h" #include -#include #include "core/ft/numtotext.h" #include "core/ft/typos.h" @@ -17,142 +16,87 @@ namespace reindexer { constexpr int kDigitUtfSizeof = 1; -class ExceptionPtrWrapper { -public: - void SetException(std::exception_ptr ptr) { - std::lock_guard lck(mtx_); - if (!ex_) { - ex_ = std::move(ptr); - } - } - void RethrowException() { - std::lock_guard lck(mtx_); - if (ex_) { - auto ptr = std::move(ex_); - ex_ = nullptr; - std::rethrow_exception(std::move(ptr)); - } - } - bool HasException() const noexcept { - std::lock_guard lck(mtx_); - return bool(ex_); - } - -private: - std::exception_ptr ex_ = nullptr; - mutable std::mutex mtx_; -}; - template void DataProcessor::Process(bool multithread) { ExceptionPtrWrapper exwr; words_map words_um; - auto tm0 = system_clock_w::now(); + const auto tm0 = system_clock_w::now(); size_t szCnt = buildWordsMap(words_um, multithread); - auto tm2 = system_clock_w::now(); + const auto tm1 = system_clock_w::now(); auto &words = holder_.GetWords(); + const size_t wrdOffset = words.size(); + holder_.SetWordsOffset(wrdOffset); - holder_.SetWordsOffset(words.size()); - size_t wrdOffset = words.size(); - - const auto found = BuildSuffix(words_um, holder_); - auto GetWordByIdFunc = [this](WordIdType id) -> PackedWordEntry & { return holder_.GetWordById(id); }; - + const auto preprocWords = insertIntoSuffix(words_um, holder_); + const auto tm2 = system_clock_w::now(); // Step 4: Commit suffixes array. It runs in parallel with next step auto &suffixes = holder_.GetSuffix(); - auto tm3 = system_clock_w::now(), tm4 = system_clock_w::now(); - auto sufBuildFun = [&suffixes, &tm3, &exwr]() { - try { - suffixes.build(); - tm3 = system_clock_w::now(); - } catch (...) { - exwr.SetException(std::current_exception()); - } - }; - std::thread sufBuildThread(sufBuildFun); + auto tm3 = tm2, tm4 = tm2; + std::thread sufBuildThread = runInThread(exwr, [&suffixes, &tm3] { + suffixes.build(); + tm3 = system_clock_w::now(); + }); // Step 5: Normalize and sort idrelsets. It runs in parallel with next step size_t idsetcnt = 0; + std::thread idrelsetCommitThread = runInThread(exwr, [&] { + idsetcnt = commitIdRelSets(preprocWords, words_um, holder_, wrdOffset); + tm4 = system_clock_w::now(); + }); - auto wIt = words.begin() + wrdOffset; - - auto idrelsetCommitFun = [&wIt, &found, &GetWordByIdFunc, &tm4, &idsetcnt, &words_um, &exwr]() { - try { - uint32_t i = 0; - for (auto keyIt = words_um.begin(), endIt = words_um.end(); keyIt != endIt; ++keyIt, ++i) { - // Pack idrelset - - PackedWordEntry *word; - - if (found.size() && !found[i].IsEmpty()) { - word = &GetWordByIdFunc(found[i]); - } else { - word = &(*wIt); - ++wIt; - idsetcnt += sizeof(*wIt); - } - - word->vids_.insert(word->vids_.end(), keyIt->second.vids_.begin(), keyIt->second.vids_.end()); - word->vids_.shrink_to_fit(); - - keyIt->second.vids_.clear(); - idsetcnt += word->vids_.heap_size(); - } - tm4 = system_clock_w::now(); - } catch (...) { - exwr.SetException(std::current_exception()); - } - }; - - std::thread idrelsetCommitThread(idrelsetCommitFun); + // Step 6: Build typos hash map + try { + buildTyposMap(wrdOffset, preprocWords); + } catch (...) { + exwr.SetException(std::current_exception()); + } + const auto tm5 = system_clock_w::now(); - // Wait for suf array build. It is neccessary for typos - sufBuildThread.join(); + // Step 7: Await threads idrelsetCommitThread.join(); + sufBuildThread.join(); exwr.RethrowException(); + const auto tm6 = system_clock_w::now(); - // Step 6: Build typos hash map - buildTyposMap(wrdOffset, found); - // print(words_um); - - auto tm5 = system_clock_w::now(); - - logPrintf(LogInfo, "FastIndexText[%d] built with [%d uniq words, %d typos, %dKB text size, %dKB suffixarray size, %dKB idrelsets size]", - holder_.steps.size(), words_um.size(), holder_.GetTyposHalf().size() + holder_.GetTyposMax().size(), szCnt / 1024, - suffixes.heap_size() / 1024, idsetcnt / 1024); + logPrintf( + LogInfo, + "FastIndexText[%d] built with [%d uniq words, %d typos (%d + %d), %dKB text size, %dKB suffixarray size, %dKB idrelsets size]", + holder_.steps.size(), words_um.size(), holder_.GetTyposHalf().size() + holder_.GetTyposMax().size(), holder_.GetTyposHalf().size(), + holder_.GetTyposMax().size(), szCnt / 1024, suffixes.heap_size() / 1024, idsetcnt / 1024); logPrintf(LogInfo, - "DataProcessor::Process elapsed %d ms total [ build words %d ms, build typos %d ms | build suffixarry %d ms | sort " - "idrelsets %d ms]", - duration_cast(tm5 - tm0).count(), duration_cast(tm2 - tm0).count(), - duration_cast(tm5 - tm4).count(), duration_cast(tm3 - tm2).count(), - duration_cast(tm4 - tm2).count()); + "DataProcessor::Process elapsed %d ms total [ build words %d ms | suffixes preproc %d ms | build typos %d ms | build " + "suffixarry %d ms | sort idrelsets %d ms]", + duration_cast(tm6 - tm0).count(), duration_cast(tm1 - tm0).count(), + duration_cast(tm2 - tm1).count(), duration_cast(tm5 - tm2).count(), + duration_cast(tm3 - tm2).count(), duration_cast(tm4 - tm2).count()); } template -std::vector DataProcessor::BuildSuffix(words_map &words_um, DataHolder &holder) { +typename DataProcessor::WordsVector DataProcessor::insertIntoSuffix(words_map &words_um, DataHolder &holder) { auto &words = holder.GetWords(); - auto &suffix = holder.GetSuffix(); suffix.reserve(words_um.size() * 20, words_um.size()); + const bool enableNumbersSearch = holder.cfg_->enableNumbersSearch; - std::vector found; - + WordsVector found; found.reserve(words_um.size()); for (auto &keyIt : words_um) { // if we still haven't whis word we add it to new suffix tree else we will only add info to current word auto id = words.size(); - WordIdType pos = found.emplace_back(holder_.findWord(keyIt.first)); + WordIdType pos = holder.findWord(keyIt.first); if (!pos.IsEmpty()) { + found.emplace_back(pos); continue; } + found.emplace_back(keyIt.first); words.emplace_back(); - pos = holder_.BuildWordId(id); - if (holder_.cfg_->enableNumbersSearch && keyIt.second.virtualWord) { + pos = holder.BuildWordId(id); + if (enableNumbersSearch && keyIt.second.virtualWord) { suffix.insert(keyIt.first, pos, kDigitUtfSizeof); } else { suffix.insert(keyIt.first, pos); @@ -162,76 +106,186 @@ std::vector DataProcessor::BuildSuffix(words_map &words_um, } template -size_t DataProcessor::buildWordsMap(words_map &words_um, bool multithread) { - ExceptionPtrWrapper exwr; - uint32_t maxIndexWorkers = multithread ? hardware_concurrency() : 1; +size_t DataProcessor::commitIdRelSets(const WordsVector &preprocWords, words_map &words_um, DataHolder &holder, + size_t wrdOffset) { + size_t idsetcnt = 0; + auto wIt = holder.GetWords().begin() + wrdOffset; + uint32_t i = 0; + auto preprocWordsSize = preprocWords.size(); + for (auto keyIt = words_um.begin(), endIt = words_um.end(); keyIt != endIt; ++keyIt, ++i) { + // Pack idrelset + PackedWordEntry *word = nullptr; + if (preprocWordsSize > i) { + if (auto widPtr = std::get_if(&preprocWords[i]); widPtr) { + assertrx_dbg(!widPtr->IsEmpty()); + word = &holder.GetWordById(*widPtr); + } + } + if (!word) { + word = &(*wIt); + ++wIt; + idsetcnt += sizeof(*wIt); + } + + word->vids.insert(word->vids.end(), std::make_move_iterator(keyIt->second.vids.begin()), + std::make_move_iterator(keyIt->second.vids.end())); + keyIt->second.vids = IdRelSet(); + word->vids.shrink_to_fit(); + idsetcnt += word->vids.heap_size(); + } + return idsetcnt; +} + +static uint32_t getMaxBuildWorkers(bool multithread) noexcept { + if (!multithread) { + return 1; + } + // using std's hardware_concurrency instead of reindexer's hardware_concurrency here + auto maxIndexWorkers = std::thread::hardware_concurrency(); if (!maxIndexWorkers) { - maxIndexWorkers = 1; + return 4; + } else if (maxIndexWorkers > 32) { + return 16; + } else if (maxIndexWorkers > 24) { + return 12; } else if (maxIndexWorkers > 8) { - maxIndexWorkers = 8; + return 8; + } + return maxIndexWorkers; +} + +template +void makeDocsDistribution(ContextT *ctxs, size_t ctxsCount, size_t docs) { + if (ctxsCount == 1) { + ctxs[0].from = 0; + ctxs[0].to = docs; + return; + } + if (docs < ctxsCount) { + for (size_t i = 0; i < docs; ++i) { + ctxs[i].from = i; + ctxs[i].to = i + 1; + } + for (size_t i = docs; i < ctxsCount; ++i) { + ctxs[i].from = 0; + ctxs[i].to = 0; + } + return; + } + const size_t part = docs / ctxsCount; + const size_t smallPart = part - (part / 8); + const size_t largePart = part + (part / 8); + size_t next = 0; + for (size_t i = 0; i < ctxsCount / 2; ++i) { + ctxs[i].from = next; + next += smallPart; + ctxs[i].to = next; } + for (size_t i = ctxsCount / 2; i < ctxsCount; ++i) { + ctxs[i].from = next; + next += largePart; + ctxs[i].to = next; + } + ctxs[ctxsCount - 1].to = docs; +} + +template +size_t DataProcessor::buildWordsMap(words_map &words_um, bool multithread) { + ExceptionPtrWrapper exwr; + uint32_t maxIndexWorkers = getMaxBuildWorkers(multithread); size_t szCnt = 0; + auto &vdocsTexts = holder_.vdocsTexts; struct context { words_map words_um; std::thread thread; + size_t from; + size_t to; + + ~context() { + if (thread.joinable()) { + thread.join(); + } + } }; std::unique_ptr ctxs(new context[maxIndexWorkers]); + makeDocsDistribution(ctxs.get(), maxIndexWorkers, vdocsTexts.size()); +#ifdef RX_WITH_STDLIB_DEBUG + size_t to = 0; + auto printDistribution = [&] { + std::cerr << "Distribution:\n"; + for (uint32_t i = 0; i < maxIndexWorkers; ++i) { + std::cerr << fmt::sprintf("%d: { from: %d; to: %d }", i, ctxs[i].from, ctxs[i].to) << std::endl; + } + }; + for (uint32_t i = 0; i < maxIndexWorkers; ++i) { + if (to == vdocsTexts.size() && (ctxs[i].from || ctxs[i].to)) { + printDistribution(); + assertrx_dbg(!ctxs[i].from); + assertrx_dbg(!ctxs[i].to); + } else if (ctxs[i].from > ctxs[i].to || (ctxs[i].from && ctxs[i].from != to)) { + printDistribution(); + assertrx_dbg(ctxs[i].from <= ctxs[i].to); + assertrx_dbg(ctxs[i].from == to); + } + to = ctxs[i].to ? ctxs[i].to : to; + } + assertrx_dbg(ctxs[0].from == 0); + assertrx_dbg(to == vdocsTexts.size()); +#endif // RX_WITH_STDLIB_DEBUG + ThreadsContainer bgThreads; auto &cfg = holder_.cfg_; - auto &vdocsTexts = holder_.vdocsTexts; auto &vdocs = holder_.vdocs_; const int fieldscount = fieldSize_; size_t offset = holder_.vdocsOffset_; - auto cycleSize = vdocsTexts.size() / maxIndexWorkers + (vdocsTexts.size() % maxIndexWorkers ? 1 : 0); // build words map parallel in maxIndexWorkers threads - auto worker = [this, &ctxs, &vdocsTexts, offset, cycleSize, fieldscount, &cfg, &vdocs, &exwr](int i) { - try { - auto ctx = &ctxs[i]; - std::string word, str; - std::vector wrds; - std::vector virtualWords; - size_t start = cycleSize * i; - size_t fin = std::min(cycleSize * (i + 1), vdocsTexts.size()); - for (VDocIdType j = start; j < fin; ++j) { - const size_t vdocId = offset + j; - auto &vdoc = vdocs[vdocId]; - vdoc.wordsCount.insert(vdoc.wordsCount.begin(), fieldscount, 0.0); - vdoc.mostFreqWordCount.insert(vdoc.mostFreqWordCount.begin(), fieldscount, 0.0); - - auto &vdocsText = vdocsTexts[j]; - for (size_t field = 0, sz = vdocsText.size(); field < sz; ++field) { - split(vdocsText[field].first, str, wrds, cfg->extraWordSymbols); - const int rfield = vdocsText[field].second; - assertrx(rfield < fieldscount); - - vdoc.wordsCount[rfield] = wrds.size(); - - int insertPos = -1; - for (auto w : wrds) { - insertPos++; - word.assign(w); - if (!word.length() || cfg->stopWords.find(word) != cfg->stopWords.end()) continue; - - auto [idxIt, emplaced] = ctx->words_um.try_emplace(word, WordEntry()); - (void)emplaced; - const int mfcnt = idxIt->second.vids_.Add(vdocId, insertPos, rfield); - if (mfcnt > vdoc.mostFreqWordCount[rfield]) { - vdoc.mostFreqWordCount[rfield] = mfcnt; - } - - if (cfg->enableNumbersSearch && is_number(word)) { - buildVirtualWord(word, ctx->words_um, vdocId, field, insertPos, virtualWords); - } + auto worker = [this, &ctxs, &vdocsTexts, offset, fieldscount, &cfg, &vdocs](int i) { + auto ctx = &ctxs[i]; + std::string str; + std::vector wrds; + std::vector virtualWords; + const size_t start = ctx->from; + const size_t fin = ctx->to; + const std::string_view extraWordSymbols(cfg->extraWordSymbols); + const bool enableNumbersSearch = cfg->enableNumbersSearch; + const word_hash h; + for (VDocIdType j = start; j < fin; ++j) { + const size_t vdocId = offset + j; + auto &vdoc = vdocs[vdocId]; + vdoc.wordsCount.resize(fieldscount, 0.0); + vdoc.mostFreqWordCount.resize(fieldscount, 0.0); + + auto &vdocsText = vdocsTexts[j]; + for (size_t field = 0, sz = vdocsText.size(); field < sz; ++field) { + split(vdocsText[field].first, str, wrds, extraWordSymbols); + const int rfield = vdocsText[field].second; + assertrx(rfield < fieldscount); + + vdoc.wordsCount[rfield] = wrds.size(); + + int insertPos = -1; + for (auto word : wrds) { + ++insertPos; + const auto whash = h(word); + if (!word.length() || cfg->stopWords.find(word, whash) != cfg->stopWords.end()) continue; + + auto [idxIt, emplaced] = ctx->words_um.try_emplace_prehashed(whash, word); + (void)emplaced; + const int mfcnt = idxIt->second.vids.Add(vdocId, insertPos, rfield); + if (mfcnt > vdoc.mostFreqWordCount[rfield]) { + vdoc.mostFreqWordCount[rfield] = mfcnt; + } + + if (enableNumbersSearch && is_number(word)) { + buildVirtualWord(word, ctx->words_um, vdocId, field, insertPos, virtualWords); } } } - } catch (...) { - exwr.SetException(std::current_exception()); } }; for (uint32_t t = 1; t < maxIndexWorkers; ++t) { - ctxs[t].thread = std::thread(worker, t); + ctxs[t].thread = runInThread(exwr, worker, t); } // If there was only 1 build thread. Just return it's build results worker(0); @@ -240,14 +294,15 @@ size_t DataProcessor::buildWordsMap(words_map &words_um, bool multithrea for (uint32_t i = 1; i < maxIndexWorkers; ++i) { auto &ctx = ctxs[i]; ctx.thread.join(); + if (exwr.HasException()) { continue; } for (auto &it : ctx.words_um) { #if defined(RX_WITH_STDLIB_DEBUG) || defined(REINDEX_WITH_ASAN) const auto fBeforeMove = it.first; - const auto sBeforeMove = it.second; - const auto sCapacityBeforeMove = it.second.vids_.capacity(); + const auto sBeforeMove = it.second.MakeCopy(); + const auto sCapacityBeforeMove = it.second.vids.capacity(); #endif // defined(RX_WITH_STDLIB_DEBUG) || defined(REINDEX_WITH_ASAN) auto [idxIt, emplaced] = words_um.try_emplace(std::move(it.first), std::move(it.second)); if (!emplaced) { @@ -255,57 +310,61 @@ size_t DataProcessor::buildWordsMap(words_map &words_um, bool multithrea // Make sure, that try_emplace did not moved the values assertrx(it.first == fBeforeMove); assertrx(it.second.virtualWord == sBeforeMove.virtualWord); - assertrx(it.second.vids_.size() == sBeforeMove.vids_.size()); - assertrx(it.second.vids_.capacity() == sCapacityBeforeMove); + assertrx(it.second.vids.size() == sBeforeMove.vids.size()); + assertrx(it.second.vids.capacity() == sCapacityBeforeMove); #endif // defined(RX_WITH_STDLIB_DEBUG) || defined(REINDEX_WITH_ASAN) - idxIt->second.vids_.reserve(it.second.vids_.size() + idxIt->second.vids_.size()); - for (auto &&r : it.second.vids_) idxIt->second.vids_.emplace_back(std::move(r)); - it.second.vids_ = IdRelSet(); + auto &resultVids = idxIt->second.vids; + auto &newVids = it.second.vids; + resultVids.insert(resultVids.end(), std::make_move_iterator(newVids.begin()), std::make_move_iterator(newVids.end())); } } - words_map().swap(ctx.words_um); + bgThreads.Add([&ctx]() noexcept { + try { + words_map().swap(ctx.words_um); + // NOLINTBEGIN(bugprone-empty-catch) + } catch (...) { + } + // NOLINTEND(bugprone-empty-catch) + }); } exwr.RethrowException(); + bgThreads.Add([this]() noexcept { std::vector, 8>>().swap(holder_.vdocsTexts); }); + bgThreads.Add([this]() noexcept { std::vector>().swap(holder_.bufStrs_); }); + // Calculate avg words count per document for bm25 calculation if (vdocs.size()) { - holder_.avgWordsCount_.resize(fieldscount); - for (int i = 0; i < fieldscount; i++) holder_.avgWordsCount_[i] = 0; - - for (auto &vdoc : vdocs) { - for (int i = 0; i < fieldscount; i++) holder_.avgWordsCount_[i] += vdoc.wordsCount[i]; + holder_.avgWordsCount_.resize(fieldscount, 0); + for (int i = 0; i < fieldscount; i++) { + auto &avgRef = holder_.avgWordsCount_[i]; + for (auto &vdoc : vdocs) avgRef += vdoc.wordsCount[i]; + avgRef /= vdocs.size(); } - for (int i = 0; i < fieldscount; i++) holder_.avgWordsCount_[i] /= vdocs.size(); } - // Check and print potential stop words - if rx_unlikely (holder_.cfg_->logLevel >= LogInfo) { + if (holder_.cfg_->logLevel >= LogInfo) { WrSerializer out; for (auto &w : words_um) { - if (w.second.vids_.size() > vdocs.size() / 5 || int64_t(w.second.vids_.size()) > holder_.cfg_->mergeLimit) { - out << w.first << "(" << w.second.vids_.size() << ") "; + if (w.second.vids.size() > vdocs.size() / 5 || int64_t(w.second.vids.size()) > holder_.cfg_->mergeLimit) { + out << w.first << "(" << w.second.vids.size() << ") "; } } logPrintf(LogInfo, "Total documents: %d. Potential stop words (with corresponding docs count): %s", vdocs.size(), out.Slice()); } - std::vector, 8>>().swap(holder_.vdocsTexts); - std::vector>().swap(holder_.bufStrs_); return szCnt; } template void DataProcessor::buildVirtualWord(std::string_view word, words_map &words_um, VDocIdType docType, int rfield, size_t insertPos, std::vector &container) { - auto &vdocs = holder_.vdocs_; - - auto &vdoc(vdocs[docType]); + auto &vdoc(holder_.vdocs_[docType]); NumToText::convert(word, container); for (std::string &numberWord : container) { WordEntry wentry; wentry.virtualWord = true; auto idxIt = words_um.emplace(std::move(numberWord), std::move(wentry)).first; - const int mfcnt = idxIt->second.vids_.Add(docType, insertPos, rfield); + const int mfcnt = idxIt->second.vids.Add(docType, insertPos, rfield); if (mfcnt > vdoc.mostFreqWordCount[rfield]) { vdoc.mostFreqWordCount[rfield] = mfcnt; } @@ -315,55 +374,107 @@ void DataProcessor::buildVirtualWord(std::string_view word, words_map &w } template -void DataProcessor::buildTyposMap(uint32_t startPos, const std::vector &found) { +void DataProcessor::buildTyposMap(uint32_t startPos, const WordsVector &preprocWords) { if (!holder_.cfg_->maxTypos) { return; } + if (preprocWords.empty()) { + return; + } - typos_context tctx[kMaxTyposInWord]; auto &typosHalf = holder_.GetTyposHalf(); auto &typosMax = holder_.GetTyposMax(); - const auto &words_ = holder_.GetWords(); - size_t wordsSize = !found.empty() ? found.size() : words_.size() - startPos; - + const auto wordsSize = preprocWords.size(); + const auto maxTypoLen = holder_.cfg_->maxTypoLen; const auto maxTyposInWord = holder_.cfg_->MaxTyposInWord(); const auto halfMaxTypos = holder_.cfg_->maxTypos / 2; if (maxTyposInWord == halfMaxTypos) { - assertrx(maxTyposInWord > 0); + assertrx_throw(maxTyposInWord > 0); + typos_context tctx[kMaxTyposInWord]; const auto multiplicator = wordsSize * (10 << (maxTyposInWord - 1)); typosHalf.reserve(multiplicator / 2, multiplicator * 5); + auto wordPos = startPos; + + for (auto &word : preprocWords) { + const auto wordString = std::get_if(&word); + if (!wordString) { + continue; + } + const auto wordId = holder_.BuildWordId(wordPos++); + mktypos(tctx, *wordString, maxTyposInWord, maxTypoLen, + typos_context::CallBack{[&typosHalf, wordId](std::string_view typo, int, const typos_context::TyposVec &positions) { + typosHalf.emplace(typo, WordTypo{wordId, positions}); + }}); + } } else { - assertrx(maxTyposInWord == halfMaxTypos + 1); + assertrx_throw(maxTyposInWord == halfMaxTypos + 1); + auto multiplicator = wordsSize * (10 << (halfMaxTypos > 1 ? (halfMaxTypos - 1) : 0)); - typosHalf.reserve(multiplicator / 2, multiplicator * 5); - multiplicator = wordsSize * (10 << (maxTyposInWord - 1)) - multiplicator; - typosMax.reserve(multiplicator / 2, multiplicator * 5); - } + ExceptionPtrWrapper exwr; + std::thread maxTyposTh = runInThread( + exwr, + [&](size_t mult) noexcept { + typos_context tctx[kMaxTyposInWord]; + auto wordPos = startPos; + mult = wordsSize * (10 << (maxTyposInWord - 1)) - mult; + typosMax.reserve(multiplicator / 2, multiplicator * 5); + for (auto &word : preprocWords) { + const auto wordString = std::get_if(&word); + if (!wordString) { + continue; + } + const auto wordId = holder_.BuildWordId(wordPos++); + mktypos(tctx, *wordString, maxTyposInWord, maxTypoLen, + typos_context::CallBack{[wordId, &typosMax, wordString](std::string_view typo, int level, + const typos_context::TyposVec &positions) { + if (level <= 1 && typo.size() != wordString->size()) { + typosMax.emplace(typo, WordTypo{wordId, positions}); + } + }}); + } + typosMax.shrink_to_fit(); + }, + multiplicator); - for (size_t i = 0; i < wordsSize; ++i) { - if (!found.empty() && !found[i].IsEmpty()) { - continue; + try { + auto wordPos = startPos; + typos_context tctx[kMaxTyposInWord]; + typosHalf.reserve(multiplicator / 2, multiplicator * 5); + for (auto &word : preprocWords) { + const auto wordString = std::get_if(&word); + if (!wordString) { + continue; + } + const auto wordId = holder_.BuildWordId(wordPos++); + mktypos(tctx, *wordString, maxTyposInWord, maxTypoLen, + typos_context::CallBack{ + [wordId, &typosHalf, wordString](std::string_view typo, int level, const typos_context::TyposVec &positions) { + if (level > 1 || typo.size() == wordString->size()) { + typosHalf.emplace(typo, WordTypo{wordId, positions}); + } + }}); + } + } catch (...) { + exwr.SetException(std::current_exception()); } - - const auto wordId = holder_.BuildWordId(startPos); - const std::string_view word = holder_.GetSuffix().word_at(holder_.GetSuffixWordId(wordId)); - mktypos(tctx, word, maxTyposInWord, holder_.cfg_->maxTypoLen, - maxTyposInWord == halfMaxTypos - ? typos_context::CallBack{[&typosHalf, wordId](std::string_view typo, int, const typos_context::TyposVec &positions) { - typosHalf.emplace(typo, WordTypo{wordId, positions}); - }} - : typos_context::CallBack{[&](std::string_view typo, int level, const typos_context::TyposVec &positions) { - if (level > 1 || typo.size() == word.size()) { - typosHalf.emplace(typo, WordTypo{wordId, positions}); - } else { - typosMax.emplace(typo, WordTypo{wordId, positions}); - } - }}); - startPos++; + maxTyposTh.join(); + exwr.RethrowException(); } - typosHalf.shrink_to_fit(); - typosMax.shrink_to_fit(); +} + +template +template +std::thread DataProcessor::runInThread(ExceptionPtrWrapper &ew, F &&f, Args &&...args) noexcept { + return std::thread( + [fw = std::forward(f), &ew](auto &&...largs) noexcept { + try { + fw(largs...); + } catch (...) { + ew.SetException(std::current_exception()); + } + }, + std::forward(args)...); } template class DataProcessor; diff --git a/cpp_src/core/ft/ft_fast/dataprocessor.h b/cpp_src/core/ft/ft_fast/dataprocessor.h index 2a2040e2f..a9bcf2c57 100644 --- a/cpp_src/core/ft/ft_fast/dataprocessor.h +++ b/cpp_src/core/ft/ft_fast/dataprocessor.h @@ -1,6 +1,8 @@ #pragma once #include #include +#include +#include #include "dataholder.h" namespace reindexer { @@ -8,21 +10,89 @@ namespace reindexer { template class DataProcessor { public: - using words_map = RHashMap; + using words_map = + tsl::hopscotch_map>, 30, true>; DataProcessor(DataHolder& holder, size_t fieldSize) : holder_(holder), fieldSize_(fieldSize) {} void Process(bool multithread); private: - size_t buildWordsMap(words_map& m, bool multithread); + using WordVariant = std::variant; - void buildVirtualWord(std::string_view word, words_map& words_um, VDocIdType docType, int rfield, size_t insertPos, - std::vector& container); + static_assert(std::is_trivially_destructible_v, "Expecting trivial destructor"); + static_assert(sizeof(WordVariant) <= 24, "Expecting same size as correspondig union"); + + class WordsVector : private std::vector { + using Base = std::vector; + + public: + void emplace_back(std::string_view word) { Base::emplace_back(word); } + void emplace_back(WordIdType word) { + assertrx_throw(!word.IsEmpty()); + Base::emplace_back(word); + } + using Base::begin; + using Base::end; + using Base::reserve; + using Base::size; + using Base::empty; + using Base::operator[]; + }; + + class ExceptionPtrWrapper { + public: + void SetException(std::exception_ptr&& ptr) noexcept { + std::lock_guard lck(mtx_); + if (!ex_) { + ex_ = std::move(ptr); + } + } + void RethrowException() { + std::lock_guard lck(mtx_); + if (ex_) { + auto ptr = std::move(ex_); + ex_ = nullptr; + std::rethrow_exception(std::move(ptr)); + } + } + bool HasException() const noexcept { + std::lock_guard lck(mtx_); + return bool(ex_); + } - void buildTyposMap(uint32_t startPos, const std::vector& found); + private: + std::exception_ptr ex_ = nullptr; + mutable std::mutex mtx_; + }; - std::vector BuildSuffix(words_map& words_um, DataHolder& holder); + class ThreadsContainer { + public: + template + void Add(F&& f) { + threads_.emplace_back(std::forward(f)); + } + ~ThreadsContainer() { + for (auto& th : threads_) { + if (th.joinable()) { + th.join(); + } + } + } + + private: + std::vector threads_; + }; + + [[nodiscard]] size_t buildWordsMap(words_map& m, bool multithread); + void buildVirtualWord(std::string_view word, words_map& words_um, VDocIdType docType, int rfield, size_t insertPos, + std::vector& container); + void buildTyposMap(uint32_t startPos, const WordsVector& preprocWords); + [[nodiscard]] static WordsVector insertIntoSuffix(words_map& words_um, DataHolder& holder); + [[nodiscard]] static size_t commitIdRelSets(const WordsVector& preprocWords, words_map& words_um, DataHolder& holder, + size_t wrdOffset); + template + [[nodiscard]] static std::thread runInThread(ExceptionPtrWrapper&, F&&, Args&&...) noexcept; DataHolder& holder_; size_t fieldSize_; diff --git a/cpp_src/core/ft/ft_fast/selecter.cc b/cpp_src/core/ft/ft_fast/selecter.cc index 814080b84..56ba3787e 100644 --- a/cpp_src/core/ft/ft_fast/selecter.cc +++ b/cpp_src/core/ft/ft_fast/selecter.cc @@ -33,6 +33,7 @@ void Selecter::prepareVariants(std::vector& variants, RV variants.clear(); std::vector variantsUtf16{{term.pattern, holder_.cfg_->rankingConfig.fullMatch}}; + variantsUtf16.reserve(256); if (synonymsDsl && (!holder_.cfg_->enableNumbersSearch || !term.opts.number)) { // Make translit and kblayout variants @@ -132,41 +133,17 @@ RX_NO_INLINE MergeData Selecter::Process(FtDSLQuery&& dsl, bool inTransa } if rx_unlikely (holder_.cfg_->logLevel >= LogInfo) { - WrSerializer wrSer; - wrSer << "variants: ["; - for (auto& variant : ctx.variants) { - if (&variant != &*ctx.variants.begin()) wrSer << ", "; - wrSer << variant.pattern; - } - wrSer << "], variants_with_low_relevancy: ["; - for (auto& variant : ctx.lowRelVariants) { - if (&variant != &*ctx.lowRelVariants.begin()) wrSer << ", "; - wrSer << variant.pattern; - } - wrSer << "], typos: ["; - if (res.term.opts.typos) { - typos_context tctx[kMaxTyposInWord]; - mktypos(tctx, res.term.pattern, holder_.cfg_->MaxTyposInWord(), holder_.cfg_->maxTypoLen, - [&wrSer](std::string_view typo, int, const typos_context::TyposVec& positions) { - wrSer << typo; - wrSer << ":("; - for (unsigned j = 0, sz = positions.size(); j < sz; ++j) { - if (j) { - wrSer << ','; - } - wrSer << positions[j]; - } - wrSer << "), "; - }); - } - logPrintf(LogInfo, "Variants: [%s]", wrSer.Slice()); + printVariants(ctx, res); } processVariants(ctx, mergeStatuses); if (res.term.opts.typos) { // Lookup typos from typos_ map and fill results TyposHandler h(*holder_.cfg_); - h(ctx.rawResults, holder_, res.term); + size_t vidsCount = h.Process(ctx.rawResults, holder_, res.term); + if (res.term.opts.op == OpOr) { + ctx.totalORVids += vidsCount; + } } } @@ -282,7 +259,7 @@ void Selecter::processStepVariants(FtSelectContext& ctx, typename DataHo if constexpr (useExternSt == FtUseExternStatuses::Yes) { bool excluded = true; - for (const auto& id : hword.vids_) { + for (const auto& id : hword.vids) { if (mergeStatuses[id.Id()] != FtMergeStatuses::kExcluded) { excluded = false; break; @@ -311,8 +288,8 @@ void Selecter::processStepVariants(FtSelectContext& ctx, typename DataHo const auto it = res.foundWords->find(glbwordId); if (it == res.foundWords->end() || it->second.first != curRawResultIdx) { - res.push_back({&hword.vids_, keyIt->first, proc, suffixes.virtual_word_len(suffixWordId)}); - const auto vidsSize = hword.vids_.size(); + res.push_back({&hword.vids, keyIt->first, proc, suffixes.virtual_word_len(suffixWordId)}); + const auto vidsSize = hword.vids.size(); res.idsCnt_ += vidsSize; if (variant.opts.op == OpOr) { ctx.totalORVids += vidsSize; @@ -320,13 +297,13 @@ void Selecter::processStepVariants(FtSelectContext& ctx, typename DataHo (*res.foundWords)[glbwordId] = std::make_pair(curRawResultIdx, res.size() - 1); if rx_unlikely (holder_.cfg_->logLevel >= LogTrace) { logPrintf(LogInfo, " matched %s '%s' of word '%s' (variant '%s'), %d vids, %d%%", suffixLen ? "suffix" : "prefix", - keyIt->first, word, variant.pattern, holder_.GetWordById(glbwordId).vids_.size(), proc); + keyIt->first, word, variant.pattern, holder_.GetWordById(glbwordId).vids.size(), proc); } ++matched; vids += vidsSize; } else { - if (ctx.rawResults[it->second.first][it->second.second].proc_ < proc) - ctx.rawResults[it->second.first][it->second.second].proc_ = proc; + if (ctx.rawResults[it->second.first][it->second.second].proc < proc) + ctx.rawResults[it->second.first][it->second.second].proc = proc; ++skipped; } } while ((keyIt++).lcp() >= int(tmpstr.length())); @@ -715,11 +692,11 @@ void Selecter::mergeIteration(TextSearchResults& rawRes, index_t rawResI // loop on subterm (word, translit, stemmmer,...) for (auto& r : rawRes) { if (!inTransaction) ThrowOnCancel(rdxCtx); - Bm25Calculator bm25{double(totalDocsCount), double(r.vids_->size()), holder_.cfg_->bm25Config.bm25k1, + Bm25Calculator bm25{double(totalDocsCount), double(r.vids->size()), holder_.cfg_->bm25Config.bm25k1, holder_.cfg_->bm25Config.bm25b}; static_assert(sizeof(bm25) <= 32, "Bm25Calculator size is greater than 32 bytes"); // cycle through the documents for the given subterm - for (auto&& relid : *r.vids_) { + for (auto&& relid : *r.vids) { static_assert((std::is_same_v && std::is_same_v) || (std::is_same_v && std::is_same_v), "Expecting relid is movable for packed vector and not movable for simple vector"); @@ -745,7 +722,7 @@ void Selecter::mergeIteration(TextSearchResults& rawRes, index_t rawResI } // Find field with max rank - auto [termRank, field] = calcTermRank(rawRes, bm25, relid, r.proc_); + auto [termRank, field] = calcTermRank(rawRes, bm25, relid, r.proc); if (!termRank) { continue; } @@ -852,11 +829,11 @@ void Selecter::mergeIterationGroup(TextSearchResults& rawRes, index_t ra // loop on subterm (word, translit, stemmmer,...) for (auto& r : rawRes) { if (!inTransaction) ThrowOnCancel(rdxCtx); - Bm25Calculator bm25(totalDocsCount, r.vids_->size(), holder_.cfg_->bm25Config.bm25k1, holder_.cfg_->bm25Config.bm25b); + Bm25Calculator bm25(totalDocsCount, r.vids->size(), holder_.cfg_->bm25Config.bm25k1, holder_.cfg_->bm25Config.bm25b); static_assert(sizeof(bm25) <= 32, "Bm25Calculator size is greater than 32 bytes"); int vid = -1; // cycle through the documents for the given subterm - for (auto&& relid : *r.vids_) { + for (auto&& relid : *r.vids) { static_assert((std::is_same_v && std::is_same_v) || (std::is_same_v && std::is_same_v), "Expecting relid is movable for packed vector and not movable for simple vector"); @@ -872,7 +849,7 @@ void Selecter::mergeIterationGroup(TextSearchResults& rawRes, index_t ra if (!vdocs[vid].keyEntry) continue; // Find field with max rank - auto [termRank, field] = calcTermRank(rawRes, bm25, relid, r.proc_); + auto [termRank, field] = calcTermRank(rawRes, bm25, relid, r.proc); if (!termRank) continue; if rx_unlikely (holder_.cfg_->logLevel >= LogTrace) { @@ -1021,12 +998,12 @@ void Selecter::mergeResultsPart(std::vector& rawResul } template -void Selecter::TyposHandler::operator()(std::vector& rawResults, const DataHolder& holder, - const FtDSLEntry& term) { +size_t Selecter::TyposHandler::Process(std::vector& rawResults, const DataHolder& holder, + const FtDSLEntry& term) { TextSearchResults& res = rawResults.back(); const unsigned curRawResultIdx = rawResults.size() - 1; - const size_t patternSize = utf16_to_utf8(term.pattern).size(); - + const size_t patternSize = utf16_to_utf8_size(term.pattern); + size_t totalVids = 0; for (auto& step : holder.steps) { typos_context tctx[kMaxTyposInWord]; const decltype(step.typosHalf_)* typoses[2]{&step.typosHalf_, &step.typosMax_}; @@ -1075,14 +1052,15 @@ void Selecter::TyposHandler::operator()(std::vector& const auto it = res.foundWords->find(wordTypo.word); if (it == res.foundWords->end() || it->second.first != curRawResultIdx) { const auto& hword = holder.GetWordById(wordTypo.word); - res.push_back({&hword.vids_, typoIt->first, proc, step.suffixes_.virtual_word_len(wordIdSfx)}); - res.idsCnt_ += hword.vids_.size(); + res.push_back({&hword.vids, typoIt->first, proc, step.suffixes_.virtual_word_len(wordIdSfx)}); + res.idsCnt_ += hword.vids.size(); res.foundWords->emplace(wordTypo.word, std::make_pair(curRawResultIdx, res.size() - 1)); logTraceF(LogInfo, " matched typo '%s' of word '%s', %d ids, %d%%", typoIt->first, - step.suffixes_.word_at(wordIdSfx), hword.vids_.size(), proc); + step.suffixes_.word_at(wordIdSfx), hword.vids.size(), proc); ++matched; - vids += hword.vids_.size(); + vids += hword.vids.size(); + totalVids += hword.vids.size(); } else { ++skiped; } @@ -1094,6 +1072,7 @@ void Selecter::TyposHandler::operator()(std::vector& logPrintf(LogInfo, "Lookup typos, matched %d typos, with %d vids, skiped %d", matched, vids, skiped); } } + return totalVids; } RX_ALWAYS_INLINE unsigned uabs(int a) { return unsigned(std::abs(a)); } @@ -1279,7 +1258,7 @@ MergeData Selecter::mergeResults(std::vector&& rawRes for (auto& rawRes : rawResults) { boost::sort::pdqsort_branchless( rawRes.begin(), rawRes.end(), - [](const TextSearchResult& lhs, const TextSearchResult& rhs) noexcept { return lhs.proc_ > rhs.proc_; }); + [](const TextSearchResult& lhs, const TextSearchResult& rhs) noexcept { return lhs.proc > rhs.proc; }); } merged.reserve(maxMergedSize); @@ -1370,6 +1349,38 @@ MergeData Selecter::mergeResults(std::vector&& rawRes return merged; } +template +void Selecter::printVariants(const FtSelectContext& ctx, const TextSearchResults& res) { + WrSerializer wrSer; + wrSer << "variants: ["; + for (auto& variant : ctx.variants) { + if (&variant != &*ctx.variants.begin()) wrSer << ", "; + wrSer << variant.pattern; + } + wrSer << "], variants_with_low_relevancy: ["; + for (auto& variant : ctx.lowRelVariants) { + if (&variant != &*ctx.lowRelVariants.begin()) wrSer << ", "; + wrSer << variant.pattern; + } + wrSer << "], typos: ["; + if (res.term.opts.typos) { + typos_context tctx[kMaxTyposInWord]; + mktypos(tctx, res.term.pattern, holder_.cfg_->MaxTyposInWord(), holder_.cfg_->maxTypoLen, + [&wrSer](std::string_view typo, int, const typos_context::TyposVec& positions) { + wrSer << typo; + wrSer << ":("; + for (unsigned j = 0, sz = positions.size(); j < sz; ++j) { + if (j) { + wrSer << ','; + } + wrSer << positions[j]; + } + wrSer << "), "; + }); + } + logPrintf(LogInfo, "Variants: [%s]", wrSer.Slice()); +} + template class Selecter; template MergeData Selecter::Process(FtDSLQuery&&, bool, FtMergeStatuses::Statuses&&, const RdxContext&); diff --git a/cpp_src/core/ft/ft_fast/selecter.h b/cpp_src/core/ft/ft_fast/selecter.h index ed2b57de5..9cb546d53 100644 --- a/cpp_src/core/ft/ft_fast/selecter.h +++ b/cpp_src/core/ft/ft_fast/selecter.h @@ -57,10 +57,10 @@ class Selecter { private: struct TextSearchResult { - const IdCont* vids_; // indexes of documents (vdoc) containing the given word + position + field + const IdCont* vids; // indexes of documents (vdoc) containing the given word + position + field std::string_view pattern; // word,translit,..... - int proc_; - int16_t wordLen_; + int proc; + int16_t wordLen; }; struct FtVariantEntry { @@ -155,7 +155,7 @@ class Selecter { useMaxLettPermDist_ = maxLettPermDist.second; } } - void operator()(std::vector&, const DataHolder&, const FtDSLEntry&); + size_t Process(std::vector&, const DataHolder&, const FtDSLEntry&); private: template @@ -256,6 +256,7 @@ class Selecter { template void processStepVariants(FtSelectContext& ctx, typename DataHolder::CommitStep& step, const FtVariantEntry& variant, unsigned curRawResultIdx, const FtMergeStatuses::Statuses& mergeStatuses, int vidsLimit); + RX_NO_INLINE void printVariants(const FtSelectContext& ctx, const TextSearchResults& res); DataHolder& holder_; size_t fieldSize_; diff --git a/cpp_src/core/ft/ft_fuzzy/baseseacher.cc b/cpp_src/core/ft/ft_fuzzy/baseseacher.cc index 181f17faa..169367607 100644 --- a/cpp_src/core/ft/ft_fuzzy/baseseacher.cc +++ b/cpp_src/core/ft/ft_fuzzy/baseseacher.cc @@ -75,30 +75,29 @@ SearchResult BaseSearcher::Compare(const BaseHolder::Ptr &holder, const FtDSLQue std::pair pos; pos.first = 0; - SmartDeque result; - std::vector rusults; + std::vector results; int max_id = 0; int min_id = INT32_MAX; if (!inTransaction) ThrowOnCancel(rdxCtx); for (auto &term : dsl) { - data_size += ParseData(holder, term.pattern, max_id, min_id, rusults, term.opts, 1); + data_size += ParseData(holder, term.pattern, max_id, min_id, results, term.opts, 1); if (holder->cfg_.enableTranslit) { searchers_[0]->GetVariants(term.pattern, data, holder->cfg_.rankingConfig.translit); - ParseData(holder, data[0].pattern, max_id, min_id, rusults, term.opts, holder->cfg_.startDefaultDecreese); + ParseData(holder, data[0].pattern, max_id, min_id, results, term.opts, holder->cfg_.startDefaultDecreese); } if (holder->cfg_.enableKbLayout) { data.clear(); searchers_[1]->GetVariants(term.pattern, data, holder->cfg_.rankingConfig.kblayout); - ParseData(holder, data[0].pattern, max_id, min_id, rusults, term.opts, holder->cfg_.startDefaultDecreese); + ParseData(holder, data[0].pattern, max_id, min_id, results, term.opts, holder->cfg_.startDefaultDecreese); } } BaseMerger mrg(max_id, min_id); - MergeCtx ctx{&rusults, &holder->cfg_, data_size, &holder->words_}; + MergeCtx ctx{&results, &holder->cfg_, data_size, &holder->words_}; auto res = mrg.Merge(ctx, inTransaction, rdxCtx); #ifdef FULL_LOG_FT @@ -126,8 +125,6 @@ void BaseSearcher::AddIndex(BaseHolder::Ptr &holder, std::string_view src_data, if (!src_data.length()) return; std::pair pos; pos.first = 0; - std::vector> res; - std::string word, str; std::wstring utf16str; std::vector wrds; split(src_data, utf16str, wrds, extraWordSymbols); diff --git a/cpp_src/core/ft/ft_fuzzy/dataholder/basebuildedholder.cc b/cpp_src/core/ft/ft_fuzzy/dataholder/basebuildedholder.cc index 9c5430309..d218341de 100644 --- a/cpp_src/core/ft/ft_fuzzy/dataholder/basebuildedholder.cc +++ b/cpp_src/core/ft/ft_fuzzy/dataholder/basebuildedholder.cc @@ -15,7 +15,7 @@ void BaseHolder::AddDada(const wchar_t *key, VDocIdType id, int pos, int field) std::wstring wkey(key, cfg_.bufferSize); auto it = tmp_data_.find(wkey); if (it == tmp_data_.end()) { - auto res = tmp_data_.emplace(wkey, IdRelSet()); + auto res = tmp_data_.try_emplace(wkey); it = res.first; } diff --git a/cpp_src/core/ft/ft_fuzzy/dataholder/basebuildedholder.h b/cpp_src/core/ft/ft_fuzzy/dataholder/basebuildedholder.h index fae48ad46..5f04f4719 100644 --- a/cpp_src/core/ft/ft_fuzzy/dataholder/basebuildedholder.h +++ b/cpp_src/core/ft/ft_fuzzy/dataholder/basebuildedholder.h @@ -27,7 +27,7 @@ struct DataStructLess { inline bool operator()(const std::wstring &ent, const std::wstring &ent1) const noexcept { return ent < ent1; } }; template -using data_map = fast_hash_map; +using data_map = tsl::hopscotch_map; typedef fast_hash_set data_set; #else diff --git a/cpp_src/core/ft/ft_fuzzy/dataholder/smardeque.cc b/cpp_src/core/ft/ft_fuzzy/dataholder/smardeque.cc index 663ce9915..d327a5e34 100644 --- a/cpp_src/core/ft/ft_fuzzy/dataholder/smardeque.cc +++ b/cpp_src/core/ft/ft_fuzzy/dataholder/smardeque.cc @@ -47,9 +47,9 @@ template void SmartDeque::allocDataPtr(size_t num) { auto tmp_data = new pointer[num]; if (data_) { - memcpy(tmp_data, data_, sizeof(pointer) * size_); + memcpy(reinterpret_cast(tmp_data), reinterpret_cast(data_), sizeof(pointer) * size_); } - memset(tmp_data + size_, 0, (num - size_) * sizeof(pointer)); + memset(reinterpret_cast(tmp_data + size_), 0, (num - size_) * sizeof(pointer)); delete[] data_; data_ = tmp_data; size_ = num; @@ -97,7 +97,8 @@ SmartDeque::SmartDeque(const SmartDeque& rhs) { } size_ = rhs.size_; data_ = new pointer[size_]; - memcpy(data_, rhs.data_, sizeof(pointer) * size_); + memcpy(reinterpret_cast(data_), reinterpret_cast(rhs.data_), sizeof(pointer) * size_); + count_ = rhs.count_; for (size_t i = 0; i < size_; ++i) { if (data_[i] != nullptr) { data_[i] = new T[block_size]; @@ -197,9 +198,20 @@ typename SmartDeque::iterator& SmartDeque::iterato } template -SmartDeque::iterator::iterator() : size_(0), offset_(0), parent_(nullptr), current_(nullptr) {} +SmartDeque::iterator::iterator() : size_(0), offset_(0), parent_(nullptr), current_(nullptr) { + if constexpr(std::is_trivial::value) { + memset(&default_data, 0, sizeof(default_data)); + } else { + static_assert(std::is_default_constructible::value, "Expecting default contractible type"); + } +} template SmartDeque::iterator::iterator(SmartDeque* parent) : size_(0), offset_(0), parent_(parent), current_(nullptr) { + if constexpr(std::is_trivial::value) { + memset(&default_data, 0, sizeof(default_data)); + } else { + static_assert(std::is_default_constructible::value, "Expecting default contractible type"); + } ++(*this); } diff --git a/cpp_src/core/ft/ftdsl.cc b/cpp_src/core/ft/ftdsl.cc index 92f606c1a..c14246bcd 100644 --- a/cpp_src/core/ft/ftdsl.cc +++ b/cpp_src/core/ft/ftdsl.cc @@ -21,7 +21,7 @@ static bool is_dslbegin(int ch, const std::string &extraWordSymbols) noexcept { ch == '\\'; } -void FtDSLQuery::parse(const std::string &q) { +void FtDSLQuery::parse(std::string_view q) { std::wstring utf16str; utf8_to_utf16(q, utf16str); parse(utf16str); diff --git a/cpp_src/core/ft/ftdsl.h b/cpp_src/core/ft/ftdsl.h index 5c7fccdaa..3f39f3c65 100644 --- a/cpp_src/core/ft/ftdsl.h +++ b/cpp_src/core/ft/ftdsl.h @@ -2,12 +2,10 @@ #include #include -#include #include #include "core/type_consts.h" -#include "estl/fast_hash_set.h" #include "estl/h_vector.h" -#include "tools/stringstools.h" +#include "stopwords/types.h" #include "usingcontainer.h" namespace reindexer { @@ -52,11 +50,10 @@ struct StopWord; class FtDSLQuery : public RVector { public: - FtDSLQuery(const RHashMap &fields, const fast_hash_set &stopWords, - const std::string &extraWordSymbols) noexcept + FtDSLQuery(const RHashMap &fields, const StopWordsSetT &stopWords, const std::string &extraWordSymbols) noexcept : fields_(fields), stopWords_(stopWords), extraWordSymbols_(extraWordSymbols) {} void parse(std::wstring &utf16str); - void parse(const std::string &q); + void parse(std::string_view q); FtDSLQuery CopyCtx() const noexcept { return {fields_, stopWords_, extraWordSymbols_}; } protected: @@ -65,7 +62,7 @@ class FtDSLQuery : public RVector { std::function resolver_; const RHashMap &fields_; - const fast_hash_set &stopWords_; + const StopWordsSetT &stopWords_; const std::string &extraWordSymbols_; }; diff --git a/cpp_src/core/ft/idrelset.cc b/cpp_src/core/ft/idrelset.cc index 6b87bc15c..73aeb76eb 100644 --- a/cpp_src/core/ft/idrelset.cc +++ b/cpp_src/core/ft/idrelset.cc @@ -19,14 +19,14 @@ size_t IdRelType::pack(uint8_t* buf) const { size_t IdRelType::unpack(const uint8_t* buf, unsigned len) { auto p = buf; - assertrx(len != 0); + assertrx_dbg(len != 0); auto l = scan_varint(len, p); - assertrx(l != 0); + assertrx_dbg(l != 0); id_ = parse_uint32(l, p); p += l, len -= l; l = scan_varint(len, p); - assertrx(l != 0); + assertrx_dbg(l != 0); int sz = parse_uint32(l, p); p += l, len -= l; @@ -35,7 +35,7 @@ size_t IdRelType::unpack(const uint8_t* buf, unsigned len) { uint32_t last = 0; for (int i = 0; i < sz; i++) { l = scan_varint(len, p); - assertrx(l != 0); + assertrx_dbg(l != 0); pos_[i].fpos = parse_uint32(l, p) + last; last = pos_[i].fpos; addField(pos_[i].field()); @@ -72,15 +72,4 @@ int IdRelType::MinPositionInField(int field) const noexcept { return res; } -int IdRelSet::Add(VDocIdType id, int pos, int field) { - if (id > max_id_) max_id_ = id; - if (id < min_id_) min_id_ = id; - - if (!size() || back().Id() != id) { - emplace_back(id); - } - back().Add(pos, field); - return back().Size(); -} - } // namespace reindexer diff --git a/cpp_src/core/ft/idrelset.h b/cpp_src/core/ft/idrelset.h index b902e37ac..3fa22d264 100644 --- a/cpp_src/core/ft/idrelset.h +++ b/cpp_src/core/ft/idrelset.h @@ -143,7 +143,14 @@ class IdRelType { class IdRelSet : public std::vector { public: - int Add(VDocIdType id, int pos, int field); + int Add(VDocIdType id, int pos, int field) { + if (id > max_id_) max_id_ = id; + if (id < min_id_) min_id_ = id; + + auto& last = (empty() || back().Id() != id) ? emplace_back(id) : back(); + last.Add(pos, field); + return last.size(); + } void SimpleCommit() noexcept { for (auto& val : *this) val.SimpleCommit(); } diff --git a/cpp_src/core/ft/stopwords/stop_ru.cc b/cpp_src/core/ft/stopwords/stop_ru.cc index 4de9a0f74..5b1b26d98 100644 --- a/cpp_src/core/ft/stopwords/stop_ru.cc +++ b/cpp_src/core/ft/stopwords/stop_ru.cc @@ -1,6 +1,6 @@ namespace reindexer { const char *stop_words_ru[] = { - // + // clang-format off "а", "е", "и", @@ -27,7 +27,6 @@ const char *stop_words_ru[] = { "могут", "можно", "может", - "мор", "моя", "моё", "мочь", @@ -39,7 +38,6 @@ const char *stop_words_ru[] = { "нами", "ними", "мимо", - "немного", "одной", "одного", "менее", @@ -55,16 +53,13 @@ const char *stop_words_ru[] = { "мало", "надо", "назад", - "наиболее", "недавно", "миллионов", "недалеко", "между", "низко", - "меля", "нельзя", "нибудь", - "непрерывно", "наконец", "никогда", "никуда", @@ -74,14 +69,11 @@ const char *stop_words_ru[] = { "нею", "неё", "них", - "мира", "наша", "наше", "наши", "ничего", - "начала", "нередко", - "несколько", "обычно", "опять", "около", @@ -89,8 +81,6 @@ const char *stop_words_ru[] = { "ну", "нх", "от", - "отовсюду", - "особенно", "нужно", "очень", "отсюда", @@ -121,11 +111,8 @@ const char *stop_words_ru[] = { "вдруг", "вы", "все", - "второй", "всем", "всеми", - "времени", - "время", "всему", "всего", "всегда", @@ -136,8 +123,6 @@ const char *stop_words_ru[] = { "всё", "всюду", "год", - "говорил", - "говорит", "года", "году", "где", @@ -184,7 +169,6 @@ const char *stop_words_ru[] = { "занята", "занято", "заняты", - "действительно", "давно", "даже", "алло", @@ -197,7 +181,6 @@ const char *stop_words_ru[] = { "лет", "зато", "даром", - "первый", "перед", "затем", "зачем", @@ -210,7 +193,6 @@ const char *stop_words_ru[] = { "при", "был", "про", - "процентов", "против", "просто", "бывает", @@ -224,7 +206,6 @@ const char *stop_words_ru[] = { "будет", "будете", "будешь", - "прекрасно", "буду", "будь", "будто", @@ -322,17 +303,13 @@ const char *stop_words_ru[] = { "самих", "саму", "чему", - "раньше", - "сейчас", "чего", - "сегодня", "себе", "тебе", "разве", "теперь", "себя", "тебя", - "седьмой", "спасибо", "слишком", "так", @@ -347,9 +324,6 @@ const char *stop_words_ru[] = { "через", "часто", "сколько", - "сказал", - "сказала", - "сказать", "ту", "ты", "эта", @@ -369,11 +343,11 @@ const char *stop_words_ru[] = { "этими", "рядом", "этих", - "третий", "тут", "эту", "суть", "чуть", "тысяч", nullptr}; -} +// clang-format on +} // namespace reindexer diff --git a/cpp_src/core/ft/stopwords/types.h b/cpp_src/core/ft/stopwords/types.h new file mode 100644 index 000000000..38a2b04cc --- /dev/null +++ b/cpp_src/core/ft/stopwords/types.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include "estl/fast_hash_set.h" +#include "tools/stringstools.h" + +namespace reindexer { + +struct StopWord : std::string { + enum class Type { Stop, Morpheme }; + StopWord(std::string base, Type type = Type::Stop) noexcept : std::string(std::move(base)), type(type) {} + Type type; +}; + +using word_hash = hash_str; +using word_equal = equal_str; +using word_less = less_str; +using StopWordsSetT = tsl::hopscotch_sc_set; + +} // namespace reindexer diff --git a/cpp_src/core/ft/usingcontainer.h b/cpp_src/core/ft/usingcontainer.h index 9f0356e13..143376670 100644 --- a/cpp_src/core/ft/usingcontainer.h +++ b/cpp_src/core/ft/usingcontainer.h @@ -26,10 +26,10 @@ class RVector : public h_vector { #endif #ifdef REINDEX_FT_EXTRA_DEBUG -template -using RHashMap = std::unordered_map; +template , typename EqualT = std::equal_to, typename LessT = std::less > +using RHashMap = std::unordered_map; #else -template -using RHashMap = fast_hash_map; +template , typename EqualT = std::equal_to, typename LessT = std::less > +using RHashMap = fast_hash_map; #endif -} // namespace reindexer \ No newline at end of file +} // namespace reindexer diff --git a/cpp_src/core/idset.h b/cpp_src/core/idset.h index e2e501c00..df16e24d3 100644 --- a/cpp_src/core/idset.h +++ b/cpp_src/core/idset.h @@ -227,5 +227,6 @@ class IdSet : public IdSetPlain { }; using IdSetRef = span; +using IdSetCRef = span; } // namespace reindexer diff --git a/cpp_src/core/index/index.h b/cpp_src/core/index/index.h index e6254dab3..5b6aac3e5 100644 --- a/cpp_src/core/index/index.h +++ b/cpp_src/core/index/index.h @@ -78,7 +78,6 @@ class Index { virtual IndexMemStat GetMemStat(const RdxContext&) = 0; virtual int64_t GetTTLValue() const noexcept { return 0; } virtual IndexIterator::Ptr CreateIterator() const { return nullptr; } - virtual bool RequireWarmupOnNsCopy() const noexcept { return false; } virtual bool IsDestroyPartSupported() const noexcept { return false; } virtual void AddDestroyTask(tsl::detail_sparse_hash::ThreadTaskQueue&) {} diff --git a/cpp_src/core/index/indextext/fastindextext.cc b/cpp_src/core/index/indextext/fastindextext.cc index 4bd72ce34..c98c4ae40 100644 --- a/cpp_src/core/index/indextext/fastindextext.cc +++ b/cpp_src/core/index/indextext/fastindextext.cc @@ -69,10 +69,8 @@ Variant FastIndexText::Upsert(const Variant &key, IdType id, bool &clearCache template void FastIndexText::Delete(const Variant &key, IdType id, StringsHolder &strHolder, bool &clearCache) { - int delcnt = 0; if rx_unlikely (key.Type().Is()) { - delcnt = this->empty_ids_.Unsorted().Erase(id); - assertrx(delcnt); + this->empty_ids_.Unsorted().Erase(id); // ignore result this->isBuilt_ = false; return; } @@ -82,7 +80,7 @@ void FastIndexText::Delete(const Variant &key, IdType id, StringsHolder &strH this->isBuilt_ = false; this->delMemStat(keyIt); - delcnt = keyIt->second.Unsorted().Erase(id); + int delcnt = keyIt->second.Unsorted().Erase(id); (void)delcnt; // TODO: we have to implement removal of composite indexes (doesn't work right now) assertf(this->opts_.IsArray() || this->Opts().IsSparse() || delcnt, "Delete unexists id from index '%s' id=%d,key=%s", this->name_, id, @@ -184,13 +182,13 @@ IdSet::Ptr FastIndexText::Select(FtCtx::Ptr fctx, FtDSLQuery &&dsl, bool inTr if (!fctx->NeedArea()) { if (useExternSt == FtUseExternStatuses::No) { appendMergedIds(mergeData, releventDocs, - [&fctx, &mergedIds](IdSetRef::iterator ebegin, IdSetRef::iterator eend, const MergeInfo &vid) { + [&fctx, &mergedIds](IdSetCRef::iterator ebegin, IdSetCRef::iterator eend, const MergeInfo &vid) { fctx->Add(ebegin, eend, vid.proc); mergedIds->Append(ebegin, eend, IdSet::Unordered); }); } else { appendMergedIds(mergeData, releventDocs, - [&fctx, &mergedIds, &statuses](IdSetRef::iterator ebegin, IdSetRef::iterator eend, const MergeInfo &vid) { + [&fctx, &mergedIds, &statuses](IdSetCRef::iterator ebegin, IdSetCRef::iterator eend, const MergeInfo &vid) { fctx->Add(ebegin, eend, vid.proc, statuses.rowIds); mergedIds->Append(ebegin, eend, statuses.rowIds, IdSet::Unordered); }); @@ -198,7 +196,7 @@ IdSet::Ptr FastIndexText::Select(FtCtx::Ptr fctx, FtDSLQuery &&dsl, bool inTr } else { if (useExternSt == FtUseExternStatuses::No) { appendMergedIds(mergeData, releventDocs, - [&fctx, &mergedIds, &mergeData](IdSetRef::iterator ebegin, IdSetRef::iterator eend, const MergeInfo &vid) { + [&fctx, &mergedIds, &mergeData](IdSetCRef::iterator ebegin, IdSetCRef::iterator eend, const MergeInfo &vid) { assertrx_throw(vid.areaIndex != std::numeric_limits::max()); fctx->Add(ebegin, eend, vid.proc, std::move(mergeData.vectorAreas[vid.areaIndex])); mergedIds->Append(ebegin, eend, IdSet::Unordered); @@ -206,7 +204,7 @@ IdSet::Ptr FastIndexText::Select(FtCtx::Ptr fctx, FtDSLQuery &&dsl, bool inTr } else { appendMergedIds( mergeData, releventDocs, - [&fctx, &mergedIds, &mergeData, &statuses](IdSetRef::iterator ebegin, IdSetRef::iterator eend, const MergeInfo &vid) { + [&fctx, &mergedIds, &mergeData, &statuses](IdSetCRef::iterator ebegin, IdSetCRef::iterator eend, const MergeInfo &vid) { assertrx_throw(vid.areaIndex != std::numeric_limits::max()); fctx->Add(ebegin, eend, vid.proc, statuses.rowIds, std::move(mergeData.vectorAreas[vid.areaIndex])); mergedIds->Append(ebegin, eend, statuses.rowIds, IdSet::Unordered); @@ -217,10 +215,9 @@ IdSet::Ptr FastIndexText::Select(FtCtx::Ptr fctx, FtDSLQuery &&dsl, bool inTr logPrintf(LogInfo, "Total merge out: %d ids", mergedIds->size()); std::string str; - for (size_t i = 0; i < fctx->GetSize();) { + for (size_t i = 0; i < fctx->Size();) { size_t j = i; - for (; j < fctx->GetSize() && fctx->Proc(i) == fctx->Proc(j); j++) - ; + for (; j < fctx->Size() && fctx->Proc(i) == fctx->Proc(j); j++); str += std::to_string(fctx->Proc(i)) + "%"; if (j - i > 1) { str += "("; @@ -230,9 +227,9 @@ IdSet::Ptr FastIndexText::Select(FtCtx::Ptr fctx, FtDSLQuery &&dsl, bool inTr str += " "; i = j; } - logPrintf(LogInfo, "Relevancy(%d): %s", fctx->GetSize(), str); + logPrintf(LogInfo, "Relevancy(%d): %s", fctx->Size(), str); } - assertrx_throw(mergedIds->size() == fctx->GetSize()); + assertrx_throw(mergedIds->size() == fctx->Size()); return mergedIds; } template diff --git a/cpp_src/core/index/indextext/fastindextext.h b/cpp_src/core/index/indextext/fastindextext.h index c394195e9..ae6f74d5b 100644 --- a/cpp_src/core/index/indextext/fastindextext.h +++ b/cpp_src/core/index/indextext/fastindextext.h @@ -19,14 +19,16 @@ class FastIndexText : public IndexText { FastIndexText(const FastIndexText& other) : Base(other) { initConfig(other.getConfig()); for (auto& idx : this->idx_map) idx.second.SetVDocID(FtKeyEntryData::ndoc); - this->CommitFulltext(); } FastIndexText(const IndexDef& idef, PayloadType&& payloadType, FieldsSet&& fields, const NamespaceCacheConfigData& cacheCfg) : Base(idef, std::move(payloadType), std::move(fields), cacheCfg) { initConfig(); } - std::unique_ptr Clone() const override { return std::make_unique>(*this); } + std::unique_ptr Clone() const override { + // Creates uncommited copy + return std::make_unique>(*this); + } IdSet::Ptr Select(FtCtx::Ptr fctx, FtDSLQuery&& dsl, bool inTransaction, FtMergeStatuses&&, FtUseExternStatuses, const RdxContext&) override final; IndexMemStat GetMemStat(const RdxContext&) override final; diff --git a/cpp_src/core/index/indextext/indextext.cc b/cpp_src/core/index/indextext/indextext.cc index f144bdf6e..dbf99e8d1 100644 --- a/cpp_src/core/index/indextext/indextext.cc +++ b/cpp_src/core/index/indextext/indextext.cc @@ -73,7 +73,7 @@ void IndexText::ReconfigureCache(const NamespaceCacheConfigData &cacheCfg) { template FtCtx::Ptr IndexText::prepareFtCtx(const BaseFunctionCtx::Ptr &ctx) { - FtCtx::Ptr ftctx = reindexer::reinterpret_pointer_cast(ctx); + FtCtx::Ptr ftctx = reindexer::static_ctx_pointer_cast(ctx); if rx_unlikely (!ftctx) { throw Error(errParams, "Full text index (%s) may not be used without context", Index::Name()); } @@ -111,7 +111,7 @@ SelectKeyResults IndexText::SelectKey(const VariantArray &keys, CondType cond if (cache_ft.valid) { if (!cache_ft.val.ids) { needPutCache = true; - } else if (ftctx->NeedArea() && (!cache_ft.val.ctx || !cache_ft.val.ctx->need_area_)) { + } else if (ftctx->NeedArea() && (!cache_ft.val.ctx || !cache_ft.val.ctx->NeedArea())) { needPutCache = true; } else { return resultFromCache(keys, std::move(cache_ft), ftctx); @@ -143,7 +143,7 @@ SelectKeyResults IndexText::doSelectKey(const VariantArray &keys, const std:: // STEP 1: Parse search query dsl FtDSLQuery dsl(this->ftFields_, this->cfg_->stopWords, this->cfg_->extraWordSymbols); - dsl.parse(keys[0].As()); + dsl.parse(keys[0].As()); IdSet::Ptr mergedIds = Select(ftctx, std::move(dsl), inTransaction, std::move(mergeStatuses), useExternSt, rdxCtx); SelectKeyResult res; @@ -152,10 +152,11 @@ SelectKeyResults IndexText::doSelectKey(const VariantArray &keys, const std:: if (ftctx->NeedArea() && need_put && mergedIds->size()) { auto config = dynamic_cast(cfg_.get()); if (config && config->maxTotalAreasToCache >= 0) { - auto d = ftctx->GetData(); + auto &d = *ftctx->GetData(); size_t totalAreas = 0; - for (auto &area : d->holders_) { - totalAreas += d->area_[area.second].GetAreasCount(); + assertrx_throw(d.holders_.has_value()); + for (auto &area : d.holders_.value()) { + totalAreas += d.area_[area.second].GetAreasCount(); } if (totalAreas > unsigned(config->maxTotalAreasToCache)) { need_put = false; @@ -164,13 +165,16 @@ SelectKeyResults IndexText::doSelectKey(const VariantArray &keys, const std:: } if (need_put && mergedIds->size()) { // This areas will be shared via cache, so lazy commit may race - auto d = ftctx->GetData(); - for (auto &area : d->holders_) { - if (!d->area_[area.second].IsCommited()) { - d->area_[area.second].Commit(); + auto dPtr = ftctx->GetData(); + auto &d = *dPtr; + if (d.holders_.has_value()) { + for (auto &area : d.holders_.value()) { + if (auto &aData = d.area_[area.second]; !aData.IsCommited()) { + aData.Commit(); + } } } - cache_ft_->Put(*ckey, FtIdSetCacheVal{IdSet::Ptr(mergedIds), std::move(d)}); + cache_ft_->Put(*ckey, FtIdSetCacheVal{IdSet::Ptr(mergedIds), std::move(dPtr)}); } res.emplace_back(std::move(mergedIds)); diff --git a/cpp_src/core/index/indextext/indextext.h b/cpp_src/core/index/indextext/indextext.h index d28038922..9580d50c3 100644 --- a/cpp_src/core/index/indextext/indextext.h +++ b/cpp_src/core/index/indextext/indextext.h @@ -46,7 +46,6 @@ class IndexText : public IndexUnordered { this->isBuilt_ = true; } void SetSortedIdxCount(int) override final {} - bool RequireWarmupOnNsCopy() const noexcept override final { return cfg_ && cfg_->enableWarmupOnNsCopy; } void DestroyCache() override { Base::DestroyCache(); cache_ft_.reset(); diff --git a/cpp_src/core/index/indexunordered.cc b/cpp_src/core/index/indexunordered.cc index 25382ca74..bfd90c2dd 100644 --- a/cpp_src/core/index/indexunordered.cc +++ b/cpp_src/core/index/indexunordered.cc @@ -204,10 +204,8 @@ Variant IndexUnordered::Upsert(const Variant &key, IdType id, bool &clearCach template void IndexUnordered::Delete(const Variant &key, IdType id, StringsHolder &strHolder, bool &clearCache) { - int delcnt = 0; if (key.Type().Is()) { - delcnt = this->empty_ids_.Unsorted().Erase(id); - assertrx(delcnt); + this->empty_ids_.Unsorted().Erase(id); // ignore result this->isBuilt_ = false; cache_.reset(); clearCache = true; @@ -215,18 +213,20 @@ void IndexUnordered::Delete(const Variant &key, IdType id, StringsHolder &str } typename T::iterator keyIt = this->idx_map.find(static_cast(key)); - if (keyIt == idx_map.end()) return; - - delMemStat(keyIt); - delcnt = keyIt->second.Unsorted().Erase(id); - (void)delcnt; - this->isBuilt_ = false; - cache_.reset(); - clearCache = true; - // TODO: we have to implement removal of composite indexes (doesn't work right now) - assertf(this->opts_.IsArray() || this->Opts().IsSparse() || delcnt, "Delete unexists id from index '%s' id=%d,key=%s (%s)", this->name_, - id, key.As(this->payloadType_, this->Fields()), + [[maybe_unused]] int delcnt = 0; + if (keyIt != idx_map.end()) { + delMemStat(keyIt); + delcnt = keyIt->second.Unsorted().Erase(id); + this->isBuilt_ = false; + cache_.reset(); + clearCache = true; + } + assertf(delcnt || this->opts_.IsArray() || this->Opts().IsSparse(), "Delete non-existing id from index '%s' id=%d,key=%s (%s)", + this->name_, id, key.As(this->payloadType_, this->Fields()), Variant(keyIt->first).As(this->payloadType_, this->Fields())); + if (keyIt == idx_map.end()) { + return; + } if (keyIt->second.Unsorted().IsEmpty()) { this->tracker_.markDeleted(keyIt); @@ -304,11 +304,11 @@ SelectKeyResults IndexUnordered::SelectKey(const VariantArray &keys, CondType const VariantArray &keys; SortType sortId; Index::SelectOpts opts; - } ctx = {&this->idx_map, keys, sortId, opts}; + bool isSparse; + } ctx = {&this->idx_map, keys, sortId, opts, this->opts_.IsSparse()}; bool selectorWasSkipped = false; - bool isSparse = this->opts_.IsSparse(); // should return true, if fallback to comparator required - auto selector = [&ctx, &selectorWasSkipped, isSparse](SelectKeyResult &res, size_t &idsCount) -> bool { + auto selector = [&ctx, &selectorWasSkipped](SelectKeyResult &res, size_t &idsCount) -> bool { idsCount = 0; // Skip this index if there are some other indexes with potentially higher selectivity if (!ctx.opts.distinct && ctx.keys.size() > 1 && 8 * ctx.keys.size() > size_t(ctx.opts.maxIterations) && @@ -328,7 +328,7 @@ SelectKeyResults IndexUnordered::SelectKey(const VariantArray &keys, CondType res.deferedExplicitSort = SelectKeyResult::IsGenericSortRecommended(res.size(), idsCount, idsCount); // avoid comparator for sparse index - if (isSparse || !ctx.opts.itemsCountInNamespace) return false; + if (ctx.isSparse || !ctx.opts.itemsCountInNamespace) return false; // Check selectivity: // if ids count too much (more than maxSelectivityPercentForIdset() of namespace), // and index not optimized, or we have >4 other conditions diff --git a/cpp_src/core/index/keyentry.h b/cpp_src/core/index/keyentry.h index 9c500dba1..169e233c1 100644 --- a/cpp_src/core/index/keyentry.h +++ b/cpp_src/core/index/keyentry.h @@ -21,11 +21,16 @@ class KeyEntry { public: IdSetT& Unsorted() noexcept { return ids_; } const IdSetT& Unsorted() const noexcept { return ids_; } - IdSetRef Sorted(unsigned sortId) const noexcept { + IdSetRef Sorted(unsigned sortId) noexcept { assertf(ids_.capacity() >= (sortId + 1) * ids_.size(), "error ids_.capacity()=%d,sortId=%d,ids_.size()=%d", ids_.capacity(), sortId, ids_.size()); return IdSetRef(ids_.data() + sortId * ids_.size(), ids_.size()); } + IdSetCRef Sorted(unsigned sortId) const noexcept { + assertf(ids_.capacity() >= (sortId + 1) * ids_.size(), "error ids_.capacity()=%d,sortId=%d,ids_.size()=%d", ids_.capacity(), sortId, + ids_.size()); + return IdSetCRef(ids_.data() + sortId * ids_.size(), ids_.size()); + } void UpdateSortedIds(const UpdateSortedContext& ctx) { ids_.reserve((ctx.getSortedIdxCount() + 1) * ids_.size()); assertrx(ctx.getCurSortId()); diff --git a/cpp_src/core/index/payload_map.h b/cpp_src/core/index/payload_map.h index 250f6b3f9..7a16a1cbb 100644 --- a/cpp_src/core/index/payload_map.h +++ b/cpp_src/core/index/payload_map.h @@ -26,54 +26,100 @@ class PayloadValueWithHash : public PayloadValue { uint32_t hash_ = 0; }; -struct equal_composite { +class equal_composite { +public: using is_transparent = void; template - equal_composite(PT &&type, FS &&fields) : type_(std::forward(type)), fields_(std::forward(fields)) {} - bool operator()(const PayloadValue &lhs, const PayloadValue &rhs) const { - assertrx(type_); - return ConstPayload(type_, lhs).IsEQ(rhs, fields_); + equal_composite(PT &&type, FS &&fields) : type_(std::forward(type)), fields_(std::forward(fields)) { + assertrx_dbg(type_); } + bool operator()(const PayloadValue &lhs, const PayloadValue &rhs) const { return ConstPayload(type_, lhs).IsEQ(rhs, fields_); } bool operator()(const PayloadValueWithHash &lhs, const PayloadValueWithHash &rhs) const { - assertrx(type_); return ConstPayload(type_, lhs).IsEQ(rhs, fields_); } - bool operator()(const PayloadValueWithHash &lhs, const PayloadValue &rhs) const { - assertrx(type_); - return ConstPayload(type_, lhs).IsEQ(rhs, fields_); + bool operator()(const PayloadValueWithHash &lhs, const PayloadValue &rhs) const { return ConstPayload(type_, lhs).IsEQ(rhs, fields_); } + bool operator()(const PayloadValue &lhs, const PayloadValueWithHash &rhs) const { return ConstPayload(type_, lhs).IsEQ(rhs, fields_); } + +private: + PayloadType type_; + FieldsSet fields_; +}; + +class equal_composite_ref { +public: + equal_composite_ref(const PayloadType &type, const FieldsSet &fields) noexcept : type_(type), fields_(fields) { + assertrx_dbg(type_.get()); } - bool operator()(const PayloadValue &lhs, const PayloadValueWithHash &rhs) const { - assertrx(type_); + bool operator()(const PayloadValue &lhs, const PayloadValue &rhs) const { + assertrx_dbg(!lhs.IsFree()); + assertrx_dbg(!rhs.IsFree()); return ConstPayload(type_, lhs).IsEQ(rhs, fields_); } - PayloadType type_; - FieldsSet fields_; + +private: + std::reference_wrapper type_; + std::reference_wrapper fields_; }; -struct hash_composite { + +class hash_composite { +public: template - hash_composite(PT &&type, FS &&fields) : type_(std::forward(type)), fields_(std::forward(fields)) {} - size_t operator()(const PayloadValueWithHash &s) const { return s.GetHash(); } - size_t operator()(const PayloadValue &s) const { - assertrx(type_); - return ConstPayload(type_, s).GetHash(fields_); + hash_composite(PT &&type, FS &&fields) : type_(std::forward(type)), fields_(std::forward(fields)) { + assertrx_dbg(type_); } + size_t operator()(const PayloadValueWithHash &s) const noexcept { return s.GetHash(); } + size_t operator()(const PayloadValue &s) const { return ConstPayload(type_, s).GetHash(fields_); } + +private: PayloadType type_; FieldsSet fields_; }; -struct less_composite { - less_composite(PayloadType &&type, FieldsSet &&fields) : type_(std::move(type)), fields_(std::move(fields)) {} +class hash_composite_ref { +public: + hash_composite_ref(const PayloadType &type, const FieldsSet &fields) noexcept : type_(type), fields_(fields) { + assertrx_dbg(type_.get()); + } + size_t operator()(const PayloadValue &s) const { return ConstPayload(type_, s).GetHash(fields_); } + +private: + std::reference_wrapper type_; + std::reference_wrapper fields_; +}; + +class less_composite { +public: + less_composite(PayloadType &&type, FieldsSet &&fields) noexcept : type_(std::move(type)), fields_(std::move(fields)) { + assertrx_dbg(type_); + } bool operator()(const PayloadValue &lhs, const PayloadValue &rhs) const { - assertrx(type_); - assertrx(!lhs.IsFree()); - assertrx(!rhs.IsFree()); + assertrx_dbg(!lhs.IsFree()); + assertrx_dbg(!rhs.IsFree()); return (ConstPayload(type_, lhs).Compare(rhs, fields_) == ComparationResult::Lt); } + +private: PayloadType type_; FieldsSet fields_; }; +class less_composite_ref { +public: + less_composite_ref(const PayloadType &type, const FieldsSet &fields) noexcept : type_(type), fields_(fields) { + assertrx_dbg(type_.get()); + } + bool operator()(const PayloadValue &lhs, const PayloadValue &rhs) const { + assertrx_dbg(!lhs.IsFree()); + assertrx_dbg(!rhs.IsFree()); + return (ConstPayload(type_, lhs).Compare(rhs, fields_) == ComparationResult::Lt); + } + +private: + std::reference_wrapper type_; + std::reference_wrapper fields_; +}; + template class payload_str_fields_helper; @@ -282,7 +328,8 @@ class payload_map : private btree::btree_map, } }; -using unordered_payload_set = tsl::hopscotch_set, 30, true>; +using unordered_payload_ref_set = + tsl::hopscotch_set, 30, true>; template constexpr bool is_payload_map_v = false; diff --git a/cpp_src/core/indexdef.cc b/cpp_src/core/indexdef.cc index 8baf07a6e..c8a01ff9a 100644 --- a/cpp_src/core/indexdef.cc +++ b/cpp_src/core/indexdef.cc @@ -5,84 +5,82 @@ #include "tools/jsontools.h" #include "tools/serializer.h" #include "type_consts_helpers.h" +#include "vendor/frozen/unordered_map.h" namespace { -static const std::vector &condsUsual() { - using namespace std::string_literals; - static const std::vector data{"SET"s, "EQ"s, "ANY"s, "EMPTY"s, "LT"s, "LE"s, "GT"s, "GE"s, "RANGE"s}; +using namespace std::string_view_literals; + +static const std::vector &condsUsual() { + static const std::vector data{"SET"sv, "EQ"sv, "ANY"sv, "EMPTY"sv, "LT"sv, "LE"sv, "GT"sv, "GE"sv, "RANGE"sv}; return data; } -static const std::vector &condsText() { - using namespace std::string_literals; - static const std::vector data{"MATCH"s}; +static const std::vector &condsText() { + static const std::vector data{"MATCH"sv}; return data; } -static const std::vector &condsBool() { - using namespace std::string_literals; - static const std::vector data{"SET"s, "EQ"s, "ANY"s, "EMPTY"s}; +static const std::vector &condsBool() { + static const std::vector data{"SET"sv, "EQ"sv, "ANY"sv, "EMPTY"sv}; return data; } -static const std::vector &condsGeom() { - using namespace std::string_literals; - static const std::vector data{"DWITHIN"s}; +static const std::vector &condsGeom() { + static const std::vector data{"DWITHIN"sv}; return data; } enum Caps { CapComposite = 0x1, CapSortable = 0x2, CapFullText = 0x4 }; struct IndexInfo { - const std::string fieldType, indexType; - const std::vector &conditions; + const std::string_view fieldType, indexType; + const std::vector &conditions; int caps; }; static const std::unordered_map, std::equal_to> &availableIndexes() { - using namespace std::string_literals; // clang-format off static const std::unordered_map, std::equal_to> data { - {IndexIntHash, {"int"s, "hash"s, condsUsual(), CapSortable}}, - {IndexInt64Hash, {"int64"s, "hash"s, condsUsual(), CapSortable}}, - {IndexStrHash, {"string"s, "hash"s, condsUsual(), CapSortable}}, - {IndexCompositeHash, {"composite"s, "hash"s, condsUsual(), CapSortable | CapComposite}}, - {IndexIntBTree, {"int"s, "tree"s, condsUsual(), CapSortable}}, - {IndexInt64BTree, {"int64"s, "tree"s, condsUsual(), CapSortable}}, - {IndexDoubleBTree, {"double"s, "tree"s, condsUsual(), CapSortable}}, - {IndexCompositeBTree, {"composite"s, "tree"s, condsUsual(), CapComposite | CapSortable}}, - {IndexStrBTree, {"string"s, "tree"s, condsUsual(), CapSortable}}, - {IndexIntStore, {"int"s, "-"s, condsUsual(), CapSortable}}, - {IndexBool, {"bool"s, "-"s, condsBool(), 0}}, - {IndexInt64Store, {"int64"s, "-"s, condsUsual(), CapSortable}}, - {IndexStrStore, {"string"s, "-"s, condsUsual(), CapSortable}}, - {IndexDoubleStore, {"double"s, "-"s, condsUsual(), CapSortable}}, - {IndexTtl, {"int64"s, "ttl"s, condsUsual(), CapSortable}}, - {IndexCompositeFastFT, {"composite"s, "text"s, condsText(), CapComposite | CapFullText}}, - {IndexCompositeFuzzyFT, {"composite"s, "fuzzytext"s, condsText(), CapComposite | CapFullText}}, - {IndexFastFT, {"string"s, "text"s, condsText(), CapFullText}}, - {IndexFuzzyFT, {"string"s, "fuzzytext"s, condsText(), CapFullText}}, - {IndexRTree, {"point"s, "rtree"s, condsGeom(), 0}}, - {IndexUuidHash, {"uuid"s, "hash"s, condsUsual(), CapSortable}}, - {IndexUuidStore, {"uuid"s, "-"s, condsUsual(), CapSortable}}, + {IndexIntHash, {"int"sv, "hash"sv, condsUsual(), CapSortable}}, + {IndexInt64Hash, {"int64"sv, "hash"sv, condsUsual(), CapSortable}}, + {IndexStrHash, {"string"sv, "hash"sv, condsUsual(), CapSortable}}, + {IndexCompositeHash, {"composite"sv, "hash"sv, condsUsual(), CapSortable | CapComposite}}, + {IndexIntBTree, {"int"sv, "tree"sv, condsUsual(), CapSortable}}, + {IndexInt64BTree, {"int64"sv, "tree"sv, condsUsual(), CapSortable}}, + {IndexDoubleBTree, {"double"sv, "tree"sv, condsUsual(), CapSortable}}, + {IndexCompositeBTree, {"composite"sv, "tree"sv, condsUsual(), CapComposite | CapSortable}}, + {IndexStrBTree, {"string"sv, "tree"sv, condsUsual(), CapSortable}}, + {IndexIntStore, {"int"sv, "-"sv, condsUsual(), CapSortable}}, + {IndexBool, {"bool"sv, "-"sv, condsBool(), 0}}, + {IndexInt64Store, {"int64"sv, "-"sv, condsUsual(), CapSortable}}, + {IndexStrStore, {"string"sv, "-"sv, condsUsual(), CapSortable}}, + {IndexDoubleStore, {"double"sv, "-"sv, condsUsual(), CapSortable}}, + {IndexTtl, {"int64"sv, "ttl"sv, condsUsual(), CapSortable}}, + {IndexCompositeFastFT, {"composite"sv, "text"sv, condsText(), CapComposite | CapFullText}}, + {IndexCompositeFuzzyFT, {"composite"sv, "fuzzytext"sv, condsText(), CapComposite | CapFullText}}, + {IndexFastFT, {"string"sv, "text"sv, condsText(), CapFullText}}, + {IndexFuzzyFT, {"string"sv, "fuzzytext"sv, condsText(), CapFullText}}, + {IndexRTree, {"point"sv, "rtree"sv, condsGeom(), 0}}, + {IndexUuidHash, {"uuid"sv, "hash"sv, condsUsual(), CapSortable}}, + {IndexUuidStore, {"uuid"sv, "-"sv, condsUsual(), CapSortable}}, }; // clang-format on return data; } -static const std::unordered_map, std::equal_to> &availableCollates() { - using namespace std::string_literals; - static const std::unordered_map, std::equal_to> data{ - {CollateASCII, "ascii"s}, {CollateUTF8, "utf8"s}, {CollateNumeric, "numeric"s}, {CollateCustom, "custom"s}, {CollateNone, "none"s}, - }; - return data; -} +constexpr static auto kAvailableCollates = frozen::make_unordered_map({ + {CollateASCII, "ascii"sv}, + {CollateUTF8, "utf8"sv}, + {CollateNumeric, "numeric"sv}, + {CollateCustom, "custom"sv}, + {CollateNone, "none"sv}, +}); -constexpr char const *kRTreeLinear = "linear"; -constexpr char const *kRTreeQuadratic = "quadratic"; -constexpr char const *kRTreeGreene = "greene"; -constexpr char const *kRTreeRStar = "rstar"; +constexpr auto kRTreeLinear = "linear"sv; +constexpr auto kRTreeQuadratic = "quadratic"sv; +constexpr auto kRTreeGreene = "greene"sv; +constexpr auto kRTreeRStar = "rstar"sv; } // namespace @@ -114,7 +112,6 @@ bool IndexDef::IsEqual(const IndexDef &other, IndexComparison cmpType) const { } IndexType IndexDef::Type() const { - using namespace std::string_view_literals; std::string_view iType = indexType_; if (iType == "") { if (fieldType_ == "double"sv) { @@ -140,7 +137,7 @@ void IndexDef::FromType(IndexType type) { indexType_ = it.indexType; } -const std::vector &IndexDef::Conditions() const { +const std::vector &IndexDef::Conditions() const noexcept { const auto it{availableIndexes().find(Type())}; assertrx(it != availableIndexes().cend()); return it->second.conditions; @@ -153,8 +150,6 @@ bool isStore(IndexType type) noexcept { type == IndexUuidStore; } -std::string IndexDef::getCollateMode() const { return availableCollates().at(opts_.GetCollateMode()); } - Error IndexDef::FromJSON(span json) { try { IndexDef::FromJSON(gason::JsonParser().Parse(json)); @@ -204,9 +199,9 @@ void IndexDef::FromJSON(const gason::JsonNode &root) { auto collateStr = root["collate_mode"].As(); if (!collateStr.empty()) { - auto collateIt = find_if(begin(availableCollates()), end(availableCollates()), - [&collateStr](const std::pair &p) { return collateStr == p.second; }); - if (collateIt == end(availableCollates())) throw Error(errParams, "Unknown collate mode %s", collateStr); + auto collateIt = find_if(begin(kAvailableCollates), end(kAvailableCollates), + [&collateStr](const std::pair &p) { return collateStr == p.second; }); + if (collateIt == end(kAvailableCollates)) throw Error(errParams, "Unknown collate mode %s", collateStr); CollateMode collateValue = collateIt->first; opts_.SetCollateMode(collateValue); if (collateValue == CollateCustom) { @@ -244,7 +239,7 @@ void IndexDef::GetJSON(WrSerializer &ser, int formatFlags) const { abort(); } } - builder.Put("collate_mode", getCollateMode()) + builder.Put("collate_mode", kAvailableCollates.at(opts_.GetCollateMode())) .Put("sort_order_letters", opts_.collateOpts_.sortOrderTable.GetSortOrderCharacters()) .Put("expire_after", expireAfter_) .Raw("config", opts_.HasConfig() ? opts_.config : "{}"); diff --git a/cpp_src/core/indexdef.h b/cpp_src/core/indexdef.h index 2f1ecd739..1ad444310 100644 --- a/cpp_src/core/indexdef.h +++ b/cpp_src/core/indexdef.h @@ -28,10 +28,9 @@ struct IndexDef { IndexDef(std::string name, JsonPaths jsonPaths, IndexType type, IndexOpts opts); bool IsEqual(const IndexDef &other, IndexComparison cmpType) const; IndexType Type() const; - std::string getCollateMode() const; - const std::vector &Conditions() const; + const std::vector &Conditions() const noexcept; void FromType(IndexType type); - [[nodiscard]] Error FromJSON(span json); + Error FromJSON(span json); void FromJSON(const gason::JsonNode &jvalue); void GetJSON(WrSerializer &ser, int formatFlags = 0) const; size_t HeapSize() const noexcept { diff --git a/cpp_src/core/item.cc b/cpp_src/core/item.cc index f7a7f555e..52322718e 100644 --- a/cpp_src/core/item.cc +++ b/cpp_src/core/item.cc @@ -34,6 +34,15 @@ KeyValueType Item::GetIndexType(int field) const noexcept { std::string_view Item::FieldRef::Name() const { return field_ >= 0 ? itemImpl_->Type().Field(field_).Name() : jsonPath_; } +template <> +Point Item::FieldRef::As() const { + auto va = (operator VariantArray()); + if (va.size() != 2) { + throw Error(errParams, "Unable to convert field with %d scalar values to 2D Point", va.size()); + } + return Point(va[0].As(), va[1].As()); +} + Item::FieldRef::operator Variant() const { VariantArray kr; if (field_ >= 0) @@ -60,7 +69,7 @@ Item::FieldRef &Item::FieldRef::operator=(Variant kr) { if (field_ >= 0) { itemImpl_->SetField(field_, VariantArray{std::move(kr)}); } else { - itemImpl_->SetField(jsonPath_, VariantArray{std::move(kr)}, nullptr); + itemImpl_->SetField(jsonPath_, VariantArray{std::move(kr)}); } return *this; @@ -73,17 +82,22 @@ Item::FieldRef &Item::FieldRef::operator=(const VariantArray &krs) { if (field_ >= 0) { itemImpl_->SetField(field_, krs); } else { - throw Error(errConflict, "Item::FieldRef::SetValue by json path not implemented yet"); + itemImpl_->SetField(jsonPath_, krs); } return *this; } template -Item::FieldRef &Item::FieldRef::operator=(span arr) { +Item::FieldRef &Item::FieldRef::operator=(span arr) { constexpr static bool kIsStr = std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v; if (field_ < 0) { - throw Error(errConflict, "Item::FieldRef::SetValue by json path not implemented yet"); + VariantArray krs; + krs.MarkArray(); + krs.reserve(arr.size()); + std::transform(arr.begin(), arr.end(), std::back_inserter(krs), [](const T &t) { return Variant(t); }); + itemImpl_->SetField(jsonPath_, krs); + return *this; } auto pl(itemImpl_->GetPayload()); @@ -124,11 +138,11 @@ Item::~Item() { } } -Error Item::FromJSON(std::string_view slice, char **endp, bool pkOnly) &noexcept { +Error Item::FromJSON(std::string_view slice, char **endp, bool pkOnly) & noexcept { RETURN_RESULT_NOEXCEPT(impl_->FromJSON(slice, endp, pkOnly)); } -Error Item::FromCJSON(std::string_view slice, bool pkOnly) &noexcept { +Error Item::FromCJSON(std::string_view slice, bool pkOnly) & noexcept { try { impl_->FromCJSON(slice, pkOnly); } @@ -141,13 +155,15 @@ std::string_view Item::GetCJSON(bool withTagsMatcher) { return impl_->GetCJSON(w std::string_view Item::GetJSON() { return impl_->GetJSON(); } -Error Item::FromMsgPack(std::string_view buf, size_t &offset) &noexcept { RETURN_RESULT_NOEXCEPT(impl_->FromMsgPack(buf, offset)); } +Error Item::FromMsgPack(std::string_view buf, size_t &offset) & noexcept { RETURN_RESULT_NOEXCEPT(impl_->FromMsgPack(buf, offset)); } + +Error Item::FromProtobuf(std::string_view sbuf) & noexcept { RETURN_RESULT_NOEXCEPT(impl_->FromProtobuf(sbuf)); } -Error Item::FromProtobuf(std::string_view sbuf) &noexcept { RETURN_RESULT_NOEXCEPT(impl_->FromProtobuf(sbuf)); } +Error Item::GetMsgPack(WrSerializer &wrser) & noexcept { RETURN_RESULT_NOEXCEPT(impl_->GetMsgPack(wrser)); } -Error Item::GetMsgPack(WrSerializer &wrser) &noexcept { RETURN_RESULT_NOEXCEPT(impl_->GetMsgPack(wrser)); } +std::string_view Item::GetMsgPack() & { return impl_->GetMsgPack(); } -Error Item::GetProtobuf(WrSerializer &wrser) &noexcept { RETURN_RESULT_NOEXCEPT(impl_->GetProtobuf(wrser)); } +Error Item::GetProtobuf(WrSerializer &wrser) & noexcept { RETURN_RESULT_NOEXCEPT(impl_->GetProtobuf(wrser)); } int Item::NumFields() const { return impl_->Type().NumFields(); } @@ -158,9 +174,9 @@ Item::FieldRef Item::operator[](int field) const { return FieldRef(field, impl_); } -Item::FieldRef Item::operator[](std::string_view name) const noexcept { +Item::FieldRef Item::FieldRefByName(std::string_view name, ItemImpl &impl) noexcept { int field = 0; - return (impl_->Type().FieldByName(name, field)) ? FieldRef(field, impl_) : FieldRef(name, impl_); + return (impl.Type().FieldByName(name, field)) ? FieldRef(field, &impl) : FieldRef(name, &impl); } int Item::GetFieldTag(std::string_view name) const { return impl_->NameTag(name); } @@ -170,7 +186,7 @@ void Item::SetPrecepts(std::vector precepts) & { impl_->SetPrecepts bool Item::IsTagsUpdated() const noexcept { return impl_->tagsMatcher().isUpdated(); } int Item::GetStateToken() const noexcept { return impl_->tagsMatcher().stateToken(); } -Item &Item::Unsafe(bool enable) &noexcept { +Item &Item::Unsafe(bool enable) & noexcept { impl_->Unsafe(enable); return *this; } @@ -178,10 +194,10 @@ Item &Item::Unsafe(bool enable) &noexcept { lsn_t Item::GetLSN() { return impl_->RealValue().IsFree() ? impl_->Value().GetLSN() : impl_->RealValue().GetLSN(); } void Item::setLSN(lsn_t lsn) { impl_->RealValue().IsFree() ? impl_->Value().SetLSN(lsn) : impl_->RealValue().SetLSN(lsn); } -template Item::FieldRef &Item::FieldRef::operator=(span arr); -template Item::FieldRef &Item::FieldRef::operator=(span arr); -template Item::FieldRef &Item::FieldRef::operator=(span arr); -template Item::FieldRef &Item::FieldRef::operator=(span); -template Item::FieldRef &Item::FieldRef::operator=(span); +template Item::FieldRef &Item::FieldRef::operator=(span arr); +template Item::FieldRef &Item::FieldRef::operator=(span arr); +template Item::FieldRef &Item::FieldRef::operator=(span arr); +template Item::FieldRef &Item::FieldRef::operator=(span); +template Item::FieldRef &Item::FieldRef::operator=(span); } // namespace reindexer diff --git a/cpp_src/core/item.h b/cpp_src/core/item.h index a63f8c2cc..22d45d306 100644 --- a/cpp_src/core/item.h +++ b/cpp_src/core/item.h @@ -35,7 +35,7 @@ class Item { Item &operator=(Item &&) noexcept; /// Reference to field. Interface for field data manipulation - class FieldRef { + class [[nodiscard]] FieldRef { friend class Item; public: @@ -52,9 +52,11 @@ class Item { /// @tparam T - type. Must be one of: int, int64_t, double or std::string /// @return value of field template - T As() { + std::enable_if_t, T> As() const { return (operator Variant()).As(); } + template + std::enable_if_t, Point> As() const; /// Set single fundamental type value /// @tparam T - type. Must be one of: int, int64_t, double /// @param val - value, which will be setted to field @@ -65,21 +67,21 @@ class Item { /// Set single point type value /// @param p - point value, which will be setted to field FieldRef &operator=(Point p) { - const double arr[]{p.X(), p.Y()}; - return operator=(span(arr, 2)); + double arr[]{p.X(), p.Y()}; + return operator=(span(arr, 2)); } /// Set array of values to field /// @tparam T - type. Must be one of: int, int64_t, double /// @param arr - std::vector of T values, which will be setted to field template - FieldRef &operator=(span arr); + FieldRef &operator=(span arr); /// Set array of values to field /// @tparam T - type. Must be one of: int, int64_t, double /// @param arr - std::vector of T values, which will be setted to field template FieldRef &operator=(const std::vector &arr) { - return operator=(span(arr)); + return operator=(span>(arr)); } /// Set string value to field /// If Item is in Unsafe Mode, then Item will not store str, but just keep pointer to str, @@ -123,32 +125,35 @@ class Item { /// @param slice - data slice with Json. /// @param endp - pointer to end of parsed part of slice /// @param pkOnly - if TRUE, that mean a JSON string will be parse only primary key fields - [[nodiscard]] Error FromJSON(std::string_view slice, char **endp = nullptr, bool pkOnly = false) &noexcept; + Error FromJSON(std::string_view slice, char **endp = nullptr, bool pkOnly = false) & noexcept; /// Build item from JSON
/// If Item is in *Unsafe Mode*, then Item will not store slice, but just keep pointer to data in slice, /// application *MUST* hold slice until end of life of Item /// @param slice - data slice with CJson /// @param pkOnly - if TRUE, that mean a JSON string will be parse only primary key fields - [[nodiscard]] Error FromCJSON(std::string_view slice, bool pkOnly = false) &noexcept; + Error FromCJSON(std::string_view slice, bool pkOnly = false) & noexcept; void FromCJSONImpl(std::string_view slice, bool pkOnly = false) &; /// Builds item from msgpack::object. /// @param buf - msgpack encoded data buffer. /// @param offset - position to start from. - [[nodiscard]] Error FromMsgPack(std::string_view buf, size_t &offset) &noexcept; + Error FromMsgPack(std::string_view buf, size_t &offset) & noexcept; /// Builds item from Protobuf /// @param sbuf - Protobuf encoded data - [[nodiscard]] Error FromProtobuf(std::string_view sbuf) &noexcept; + Error FromProtobuf(std::string_view sbuf) & noexcept; /// Packs data in msgpack format /// @param wrser - buffer to serialize data to - [[nodiscard]] Error GetMsgPack(WrSerializer &wrser) &noexcept; + Error GetMsgPack(WrSerializer &wrser) & noexcept; + /// Packs data in msgpack format + /// @return data slice with MsgPack + [[nodiscard]] std::string_view GetMsgPack() &; /// Packs item data to Protobuf /// @param wrser - buffer to serialize data to - [[nodiscard]] Error GetProtobuf(WrSerializer &wrser) &noexcept; + Error GetProtobuf(WrSerializer &wrser) & noexcept; /// Serialize item to CJSON.
/// If Item is in *Unsafe Mode*, then returned slice is allocated in temporary buffer, and can be invalidated by any next operation @@ -163,7 +168,7 @@ class Item { /// Get status of item /// @return data slice with JSON. Returned slice is allocated in temporary Item's buffer, and can be invalidated by any next /// operation with Item - [[nodiscard]] Error Status() const noexcept { return status_; } + Error Status() const noexcept { return status_; } /// Get internal ID of item /// @return ID of item [[nodiscard]] int GetID() const noexcept { return id_; } @@ -179,11 +184,11 @@ class Item { /// Get field by number /// @param field - number of field. Must be >= 0 && < NumFields /// @return FieldRef which contains reference to indexed field - [[nodiscard]] FieldRef operator[](int field) const; + FieldRef operator[](int field) const; /// Get field by name /// @param name - name of field /// @return FieldRef which contains reference to indexed field - [[nodiscard]] FieldRef operator[](std::string_view name) const noexcept; + FieldRef operator[](std::string_view name) const noexcept { return FieldRefByName(name, *impl_); } /// Get field's name tag /// @param name - field name /// @return name's numeric tag value @@ -210,10 +215,15 @@ class Item { /// The advantage of unsafe mode is speed. It does not call extra memory allocation from heap and copying data.
/// The disadvantage of unsafe mode is potentially danger code. Most of C++ stl containters in many cases invalidates references - /// and in unsafe mode caller is responsibe to guarantee, that all resources passed to Item will keep valid - Item &Unsafe(bool enable = true) &noexcept; + Item &Unsafe(bool enable = true) & noexcept; /// Get index type by field id /// @return either index type or Undefined (if index with this number does not exist or PayloadType is not available) KeyValueType GetIndexType(int field) const noexcept; + /// Get field's ref by name + /// @param name - field name + /// @param itemImpl - item + /// @return field's ref + static FieldRef FieldRefByName(std::string_view name, ItemImpl &itemImpl) noexcept; private: explicit Item(ItemImpl *impl) : impl_(impl) {} diff --git a/cpp_src/core/itemimpl.cc b/cpp_src/core/itemimpl.cc index 9c8363b8b..21a8b3c53 100644 --- a/cpp_src/core/itemimpl.cc +++ b/cpp_src/core/itemimpl.cc @@ -13,6 +13,7 @@ namespace reindexer { void ItemImpl::SetField(int field, const VariantArray &krs) { + validateModifyArray(krs); cjson_ = std::string_view(); payloadValue_.Clone(); if (!unsafe_ && !krs.empty() && krs[0].Type().Is() && @@ -24,19 +25,19 @@ void ItemImpl::SetField(int field, const VariantArray &krs) { holder_->push_back(kr.As()); krsCopy.emplace_back(p_string{&holder_->back()}); } - GetPayload().Set(field, krsCopy, false); - } else { GetPayload().Set(field, krs, false); } } -void ItemImpl::ModifyField(std::string_view jsonPath, const VariantArray &keys, const IndexExpressionEvaluator &ev, FieldModifyMode mode) { - ModifyField(tagsMatcher_.path2indexedtag(jsonPath, ev, mode != FieldModeDrop), keys, mode); +void ItemImpl::ModifyField(std::string_view jsonPath, const VariantArray &keys, FieldModifyMode mode) { + ModifyField(tagsMatcher_.path2indexedtag(jsonPath, mode != FieldModeDrop), keys, mode); } void ItemImpl::ModifyField(const IndexedTagsPath &tagsPath, const VariantArray &keys, FieldModifyMode mode) { + validateModifyArray(keys); + payloadValue_.Clone(); Payload pl = GetPayload(); ser_.Reset(); @@ -75,19 +76,19 @@ void ItemImpl::ModifyField(const IndexedTagsPath &tagsPath, const VariantArray & pl.Set(0, Variant(p_string(reinterpret_cast(tupleData_.get())), Variant::no_hold_t{})); } -void ItemImpl::SetField(std::string_view jsonPath, const VariantArray &keys, const IndexExpressionEvaluator &ev) { - ModifyField(jsonPath, keys, ev, FieldModeSet); -} -void ItemImpl::DropField(std::string_view jsonPath, const IndexExpressionEvaluator &ev) { ModifyField(jsonPath, {}, ev, FieldModeDrop); } +void ItemImpl::SetField(std::string_view jsonPath, const VariantArray &keys) { ModifyField(jsonPath, keys, FieldModeSet); } +void ItemImpl::DropField(std::string_view jsonPath) { ModifyField(jsonPath, {}, FieldModeDrop); } Variant ItemImpl::GetField(int field) { return GetPayload().Get(field, 0); } void ItemImpl::GetField(int field, VariantArray &values) { GetPayload().Get(field, values); } Error ItemImpl::FromMsgPack(std::string_view buf, size_t &offset) { + payloadValue_.Clone(); Payload pl = GetPayload(); if (!msgPackDecoder_) { msgPackDecoder_.reset(new MsgPackDecoder(tagsMatcher_)); } + pl.Reset(); ser_.Reset(); ser_.PutUInt32(0); Error err = msgPackDecoder_->Decode(buf, pl, ser_, offset); @@ -100,9 +101,11 @@ Error ItemImpl::FromMsgPack(std::string_view buf, size_t &offset) { Error ItemImpl::FromProtobuf(std::string_view buf) { assertrx(ns_); + payloadValue_.Clone(); Payload pl = GetPayload(); ProtobufDecoder decoder(tagsMatcher_, schema_); + pl.Reset(); ser_.Reset(); ser_.PutUInt32(0); Error err = decoder.Decode(buf, pl, ser_); @@ -125,6 +128,15 @@ Error ItemImpl::GetMsgPack(WrSerializer &wrser) { return Error(); } +std::string_view ItemImpl::GetMsgPack() { + ser_.Reset(); + auto err = GetMsgPack(ser_); + if (!err.ok()) { + throw err; + } + return ser_.Slice(); +} + Error ItemImpl::GetProtobuf(WrSerializer &wrser) { assertrx(ns_); ConstPayload pl = GetConstPayload(); @@ -136,7 +148,7 @@ Error ItemImpl::GetProtobuf(WrSerializer &wrser) { // Construct item from compressed json void ItemImpl::FromCJSON(std::string_view slice, bool pkOnly, Recoder *recoder) { - GetPayload().Reset(); + payloadValue_.Clone(); std::string_view data = slice; if (!unsafe_) { sourceData_.reset(new char[slice.size()]); @@ -157,6 +169,7 @@ void ItemImpl::FromCJSON(std::string_view slice, bool pkOnly, Recoder *recoder) Serializer rdser(data); Payload pl = GetPayload(); + pl.Reset(); if (!holder_) holder_ = std::make_unique>(); CJsonDecoder decoder(tagsMatcher_, *holder_); @@ -182,6 +195,7 @@ void ItemImpl::FromCJSON(std::string_view slice, bool pkOnly, Recoder *recoder) } Error ItemImpl::FromJSON(std::string_view slice, char **endp, bool pkOnly) { + payloadValue_.Clone(); std::string_view data = slice; cjson_ = std::string_view(); @@ -205,8 +219,7 @@ Error ItemImpl::FromJSON(std::string_view slice, char **endp, bool pkOnly) { } } - payloadValue_.Clone(); - size_t len; + size_t len = 0; gason::JsonNode node; gason::JsonParser parser(&largeJSONStrings_); try { @@ -222,6 +235,7 @@ Error ItemImpl::FromJSON(std::string_view slice, char **endp, bool pkOnly) { // Split parsed json into indexes and tuple JsonDecoder decoder(tagsMatcher_, pkOnly && !pkFields_.empty() ? &pkFields_ : nullptr); Payload pl = GetPayload(); + pl.Reset(); ser_.Reset(); ser_.PutUInt32(0); @@ -233,9 +247,9 @@ Error ItemImpl::FromJSON(std::string_view slice, char **endp, bool pkOnly) { return err; } -void ItemImpl::FromCJSON(ItemImpl *other, Recoder *recoder) { - FromCJSON(other->GetCJSON(), false, recoder); - cjson_ = std::string_view(); +void ItemImpl::FromCJSON(ItemImpl &other, Recoder *recoder) { + FromCJSON(other.GetCJSON(), false, recoder); + cjson_ = {}; } std::string_view ItemImpl::GetJSON() { @@ -314,4 +328,18 @@ VariantArray ItemImpl::GetValueByJSONPath(std::string_view jsonPath) { return krefs; } +void ItemImpl::validateModifyArray(const VariantArray &values) { + for (const auto &v : values) { + v.Type().EvaluateOneOf([](OneOf) {}, + [](KeyValueType::Tuple) { + throw Error(errParams, + "Unable to use 'tuple'-value (array of arrays, array of points, etc) to modify item"); + }, + [](KeyValueType::Composite) { + throw Error(errParams, "Unable to use 'composite'-value (object, array of objects, etc) to modify item"); + }); + } +} + } // namespace reindexer diff --git a/cpp_src/core/itemimpl.h b/cpp_src/core/itemimpl.h index ce3d0b10b..b17a12cac 100644 --- a/cpp_src/core/itemimpl.h +++ b/cpp_src/core/itemimpl.h @@ -22,7 +22,7 @@ class ItemImpl : public ItemImplRawData { // Construct empty item ItemImpl(PayloadType type, const TagsMatcher &tagsMatcher, const FieldsSet &pkFields = {}, std::shared_ptr schema = {}) - : ItemImplRawData(PayloadValue(type.TotalSize(), 0, type.TotalSize() + 0x100)), + : ItemImplRawData(PayloadValue(type.TotalSize(), nullptr, type.TotalSize() + 0x100)), payloadType_(std::move(type)), tagsMatcher_(tagsMatcher), pkFields_(pkFields), @@ -49,11 +49,11 @@ class ItemImpl : public ItemImplRawData { ItemImpl &operator=(ItemImpl &&) = default; ItemImpl &operator=(const ItemImpl &) = delete; - void ModifyField(std::string_view jsonPath, const VariantArray &keys, const IndexExpressionEvaluator &ev, FieldModifyMode mode); + void ModifyField(std::string_view jsonPath, const VariantArray &keys, FieldModifyMode mode); void ModifyField(const IndexedTagsPath &tagsPath, const VariantArray &keys, FieldModifyMode mode); void SetField(int field, const VariantArray &krs); - void SetField(std::string_view jsonPath, const VariantArray &keys, const IndexExpressionEvaluator &ev); - void DropField(std::string_view jsonPath, const IndexExpressionEvaluator &ev); + void SetField(std::string_view jsonPath, const VariantArray &keys); + void DropField(std::string_view jsonPath); Variant GetField(int field); void GetField(int field, VariantArray &); FieldsSet PkFields() const { return pkFields_; } @@ -68,7 +68,7 @@ class ItemImpl : public ItemImplRawData { std::string_view GetJSON(); Error FromJSON(std::string_view slice, char **endp = nullptr, bool pkOnly = false); - void FromCJSON(ItemImpl *other, Recoder *); + void FromCJSON(ItemImpl &other, Recoder *); std::string_view GetCJSON(bool withTagsMatcher = false); std::string_view GetCJSON(WrSerializer &ser, bool withTagsMatcher = false); @@ -78,6 +78,7 @@ class ItemImpl : public ItemImplRawData { Error FromMsgPack(std::string_view sbuf, size_t &offset); Error FromProtobuf(std::string_view sbuf); Error GetMsgPack(WrSerializer &wrser); + std::string_view GetMsgPack(); Error GetProtobuf(WrSerializer &wrser); const PayloadType &Type() const noexcept { return payloadType_; } @@ -117,8 +118,9 @@ class ItemImpl : public ItemImplRawData { } void SetNamespace(std::shared_ptr ns) noexcept { ns_ = std::move(ns); } std::shared_ptr GetNamespace() const noexcept { return ns_; } + static void validateModifyArray(const VariantArray &values); -protected: +private: // Index fields payload data PayloadType payloadType_; PayloadValue realValue_; diff --git a/cpp_src/core/itemimplrawdata.h b/cpp_src/core/itemimplrawdata.h index 176e212ed..9050c7a28 100644 --- a/cpp_src/core/itemimplrawdata.h +++ b/cpp_src/core/itemimplrawdata.h @@ -10,7 +10,7 @@ namespace reindexer { struct ItemImplRawData { ItemImplRawData() = default; - ItemImplRawData(PayloadValue v) : payloadValue_(std::move(v)) {} + explicit ItemImplRawData(PayloadValue v) : payloadValue_(std::move(v)) {} ItemImplRawData(const ItemImplRawData &) = delete; ItemImplRawData(ItemImplRawData &&) = default; ItemImplRawData &operator=(const ItemImplRawData &) = delete; diff --git a/cpp_src/core/itemmodifier.cc b/cpp_src/core/itemmodifier.cc index 73e143982..9298a886c 100644 --- a/cpp_src/core/itemmodifier.cc +++ b/cpp_src/core/itemmodifier.cc @@ -7,13 +7,71 @@ namespace reindexer { -const std::string &ItemModifier::FieldData::name() const noexcept { return entry_.Column(); } +std::string_view ItemModifier::FieldData::Name() const noexcept { return entry_.Column(); } + +void ItemModifier::FieldData::appendAffectedIndexes(const NamespaceImpl &ns, CompositeFlags &affectedComposites) const { + const auto firstCompositePos = ns.indexes_.firstCompositePos(); + const auto firstSparsePos = ns.indexes_.firstSparsePos(); + const auto totalIndexes = ns.indexes_.totalSize(); + const bool isRegularIndex = IsIndex() && Index() < firstSparsePos; + const bool isSparseIndex = IsIndex() && Index() >= firstSparsePos && Index() < firstCompositePos; + if (isSparseIndex) { + // Composite indexes can not be created over sparse indexes, so just skipping rest of the checks for them + return; + } + const bool isCompositeIndex = IsIndex() && Index() >= firstCompositePos; + if (isCompositeIndex) { + // Composite indexes can not be created over another composite indexes, so just skipping rest of the checks for them + return; + } + std::bitset affected; + if (isRegularIndex) { + affected.set(Index()); + } else { + for (int i = 0; i < firstSparsePos; ++i) { + const auto &ptField = ns.payloadType_.Field(i); + for (const auto &jpath : ptField.JsonPaths()) { + auto tp = ns.tagsMatcher_.path2tag(jpath); + if (Tagspath().IsNestedOrEqualTo(tp)) { + affected.set(i); + break; + } + } + } + } -class ItemModifier::RollBack_ModifiedPayload : private RollBackBase { + for (int i = firstCompositePos; i < totalIndexes; ++i) { + const auto &fields = ns.indexes_[i]->Fields(); + const auto idxId = i - firstCompositePos; + + for (const auto f : fields) { + if (f == IndexValueType::SetByJsonPath) continue; + if (affected.test(f)) { + affectedComposites[idxId] = true; + break; + } + } + if (affectedComposites[idxId]) { + continue; + } + + if (!IsIndex()) { + // Fulltext composites may be created over non-index fields + for (size_t tp = 0, end = fields.getTagsPathsLength(); tp < end; ++tp) { + if (Tagspath().IsNestedOrEqualTo(fields.getTagsPath(tp))) { + affectedComposites[idxId] = true; + break; + } + } + } + } +} + +class ItemModifier::RollBack_ModifiedPayload final : private RollBackBase { public: RollBack_ModifiedPayload(ItemModifier &modifier, IdType id) noexcept : modifier_{modifier}, itemId_{id} {} RollBack_ModifiedPayload(RollBack_ModifiedPayload &&) noexcept = default; - ~RollBack_ModifiedPayload() { RollBack(); } + ~RollBack_ModifiedPayload() override { RollBack(); } void RollBack() { if (IsDisabled()) return; @@ -112,51 +170,49 @@ class ItemModifier::RollBack_ModifiedPayload : private RollBackBase { IdType itemId_; }; -ItemModifier::FieldData::FieldData(const UpdateEntry &entry, NamespaceImpl &ns) +ItemModifier::FieldData::FieldData(const UpdateEntry &entry, NamespaceImpl &ns, CompositeFlags &affectedComposites) : entry_(entry), tagsPathWithLastIndex_{std::nullopt}, arrayIndex_(IndexValueType::NotSet), isIndex_(false) { if (ns.tryGetIndexByName(entry_.Column(), fieldIndex_)) { isIndex_ = true; - auto jsonPathsSize = (ns.indexes_[fieldIndex_]->Opts().IsSparse() || static_cast(fieldIndex_) >= ns.payloadType_.NumFields()) - ? ns.indexes_[fieldIndex_]->Fields().size() + const auto &idx = *ns.indexes_[fieldIndex_]; + auto jsonPathsSize = (idx.Opts().IsSparse() || static_cast(fieldIndex_) >= ns.payloadType_.NumFields()) + ? idx.Fields().size() : ns.payloadType_.Field(fieldIndex_).JsonPaths().size(); if (jsonPathsSize != 1) { throw Error(errParams, "Ambiguity when updating field with several json paths by index name: '%s'", entry_.Column()); } - if (!entry.IsExpression()) { - const auto &fields{ns.indexes_[fieldIndex_]->Fields()}; - if (fields.size() != 1) { - throw Error(errParams, "Cannot update composite index: '%s'", entry_.Column()); - } - if (fields[0] == IndexValueType::SetByJsonPath) { - if (fields.isTagsPathIndexed(0)) { - tagsPath_ = fields.getIndexedTagsPath(0); - } else { - tagsPath_ = IndexedTagsPath{fields.getTagsPath(0)}; - } + const auto &fields{idx.Fields()}; + if (fields.size() != 1) { + throw Error(errParams, "Cannot update composite index: '%s'", entry_.Column()); + } + if (fields[0] == IndexValueType::SetByJsonPath) { + if (fields.isTagsPathIndexed(0)) { + tagsPath_ = fields.getIndexedTagsPath(0); } else { - tagsPath_ = ns.tagsMatcher_.path2indexedtag(ns.payloadType_.Field(fieldIndex_).JsonPaths()[0], nullptr, true); - } - if (tagsPath_.empty()) { - throw Error(errParams, "Cannot find field by json: '%s'", entry_.Column()); - } - if (tagsPath_.back().IsWithIndex()) { - arrayIndex_ = tagsPath_.back().Index(); - tagsPath_.back().SetIndex(IndexValueType::NotSet); + tagsPath_ = IndexedTagsPath{fields.getTagsPath(0)}; } + } else { + fieldIndex_ = fields[0]; // 'Composite' index with single subindex + tagsPath_ = ns.tagsMatcher_.path2indexedtag(ns.payloadType_.Field(fieldIndex_).JsonPaths()[0], true); + } + if (tagsPath_.empty()) { + throw Error(errParams, "Cannot find field by json: '%s'", entry_.Column()); + } + if (tagsPath_.back().IsWithIndex()) { + arrayIndex_ = tagsPath_.back().Index(); + tagsPath_.back().SetIndex(IndexValueType::NotSet); } } else if (fieldIndex_ = ns.payloadType_.FieldByJsonPath(entry_.Column()); fieldIndex_ > 0) { isIndex_ = true; - if (!entry.IsExpression()) { - tagsPath_ = ns.tagsMatcher_.path2indexedtag(entry_.Column(), nullptr, true); - if (tagsPath_.empty()) { - throw Error(errParams, "Cannot find field by json: '%s'", entry_.Column()); - } + tagsPath_ = ns.tagsMatcher_.path2indexedtag(entry_.Column(), true); + if (tagsPath_.empty()) { + throw Error(errParams, "Cannot find field by json: '%s'", entry_.Column()); } } else { TagsPath tp; - IndexedTagsPath tagsPath = ns.tagsMatcher_.path2indexedtag(entry_.Column(), nullptr, true); + IndexedTagsPath tagsPath = ns.tagsMatcher_.path2indexedtag(entry_.Column(), true); std::string jsonPath; for (size_t i = 0; i < tagsPath.size(); ++i) { if (i) jsonPath += '.'; @@ -171,63 +227,51 @@ ItemModifier::FieldData::FieldData(const UpdateEntry &entry, NamespaceImpl &ns) fieldIndex_ = 0; isIndex_ = ns.getIndexByNameOrJsonPath(jsonPath, fieldIndex_) || ns.getSparseIndexByJsonPath(jsonPath, fieldIndex_); } - if (!entry.IsExpression()) { - tagsPath_ = std::move(tagsPath); - if (tagsPath_.empty()) { - throw Error(errParams, "Cannot find field by json: '%s'", entry_.Column()); - } - if (isIndex_) { - auto &lastTag = tagsPath_.back(); - if (lastTag.IsWithIndex()) { - tagsPathWithLastIndex_ = tagsPath_; - arrayIndex_ = lastTag.Index(); - lastTag.SetIndex(IndexValueType::NotSet); - } - } + tagsPath_ = std::move(tagsPath); + if (tagsPath_.empty()) { + throw Error(errParams, "Cannot find field by json: '%s'", entry_.Column()); } - } -} - -void ItemModifier::FieldData::updateTagsPath(TagsMatcher &tm, const IndexExpressionEvaluator &ev) { - if (tagsPath_.empty()) { - tagsPath_ = tm.path2indexedtag(entry_.Column(), ev, true); - } - for (size_t i = 0; i < tagsPath_.size(); ++i) { - if (tagsPath_[i].IsWithExpression()) { - IndexedPathNode &node = tagsPath_[i]; - VariantArray vals = ev(node.Expression()); - if (vals.size() != 1) { - throw Error(errParams, "Index expression has wrong syntax: '%s'", node.Expression()); + if (isIndex_) { + auto &lastTag = tagsPath_.back(); + if (lastTag.IsWithIndex()) { + tagsPathWithLastIndex_ = tagsPath_; + arrayIndex_ = lastTag.Index(); + lastTag.SetIndex(IndexValueType::NotSet); } - vals.front().Type().EvaluateOneOf([](OneOf) noexcept {}, - [&](OneOf) { - throw Error(errParams, "Wrong type of index: '%s'", node.Expression()); - }); - node.SetIndex(vals.front().As()); - } - } - if (tagsPath_.size()) { - auto &lastTag = tagsPath_.back(); - if (lastTag.IsWithIndex()) { - arrayIndex_ = lastTag.Index(); - tagsPathWithLastIndex_ = tagsPath_; - lastTag.SetIndex(IndexValueType::NotSet); } } + appendAffectedIndexes(ns, affectedComposites); } ItemModifier::ItemModifier(const std::vector &updateEntries, NamespaceImpl &ns, - h_vector &replUpdates, const NsContext &ctx) - : ns_(ns), updateEntries_(updateEntries), rollBackIndexData_(ns_.indexes_.totalSize()) { + h_vector &replUpdates, const NsContext &ctx) + : ns_(ns), + updateEntries_(updateEntries), + rollBackIndexData_(ns_.indexes_.totalSize()), + affectedComposites_(ns_.indexes_.totalSize() - ns_.indexes_.firstCompositePos(), false) { const auto oldTmV = ns_.tagsMatcher_.version(); for (const UpdateEntry &updateField : updateEntries_) { - fieldsToModify_.emplace_back(updateField, ns_); + for (const auto &v : updateField.Values()) { + v.Type().EvaluateOneOf([](OneOf) {}, + [](KeyValueType::Tuple) { + throw Error( + errParams, + "Unable to use 'tuple'-value (array of arrays, array of points, etc) in UPDATE-query. Only " + "single dimensional arrays and arrays of objects are supported"); + }, + [](KeyValueType::Composite) { + throw Error(errParams, + "Unable to use 'composite'-value (object, array of objects, etc) in UPDATE-query. " + "Probably 'object'/'json' type was not explicitly set in the query"); + }); + } + fieldsToModify_.emplace_back(updateField, ns_, affectedComposites_); } ns_.replicateTmUpdateIfRequired(replUpdates, oldTmV, ctx); } -[[nodiscard]] bool ItemModifier::Modify(IdType itemId, const NsContext &ctx, h_vector &replUpdates) { +[[nodiscard]] bool ItemModifier::Modify(IdType itemId, const NsContext &ctx, h_vector &replUpdates) { PayloadValue &pv = ns_.items_[itemId]; Payload pl(ns_.payloadType_, pv); pv.Clone(pl.RealSize()); @@ -237,54 +281,44 @@ ItemModifier::ItemModifier(const std::vector &updateEntries, Namesp FunctionExecutor funcExecutor(ns_, replUpdates); ExpressionEvaluator ev(ns_.payloadType_, ns_.tagsMatcher_, funcExecutor); - h_vector needUpdateCompIndexes(unsigned(ns_.indexes_.compositeIndexesSize()), false); - for (FieldData &field : fieldsToModify_) { - deleteDataFromComposite(itemId, field, needUpdateCompIndexes); - } - - const auto firstCompositePos = ns_.indexes_.firstCompositePos(); - const auto totalIndexes = ns_.indexes_.totalSize(); - + deleteItemFromComposite(itemId); try { VariantArray values; for (FieldData &field : fieldsToModify_) { // values must be assigned a value in if else below - if (field.details().IsExpression()) { - assertrx(field.details().Values().size() > 0); - values = ev.Evaluate(static_cast(field.details().Values().front()), pv, field.name(), ctx); - field.updateTagsPath(ns_.tagsMatcher_, [&ev, &pv, &field, &ctx](std::string_view expression) { - return ev.Evaluate(expression, pv, field.name(), ctx); - }); + if (field.Details().IsExpression()) { + assertrx(field.Details().Values().size() > 0); + values = ev.Evaluate(static_cast(field.Details().Values().front()), pv, field.Name(), ctx); } else { - values = field.details().Values(); + values = field.Details().Values(); } - if (values.IsArrayValue() && field.tagspathWithLastIndex().back().IsArrayNode()) { + if (values.IsArrayValue() && field.TagspathWithLastIndex().back().IsArrayNode()) { throw Error(errParams, "Array items are supposed to be updated with a single value, not an array"); } - if (field.details().Mode() == FieldModeSetJson || !field.isIndex()) { + if (field.Details().Mode() == FieldModeSetJson || !field.IsIndex()) { modifyCJSON(itemId, field, values, replUpdates, ctx); } else { modifyField(itemId, field, pl, values); } } } catch (...) { - insertItemIntoCompositeIndexes(itemId, firstCompositePos, totalIndexes, needUpdateCompIndexes); + insertItemIntoComposite(itemId); throw; } - insertItemIntoCompositeIndexes(itemId, firstCompositePos, totalIndexes, needUpdateCompIndexes); + insertItemIntoComposite(itemId); if (rollBackIndexData_.IsPkModified()) { ns_.checkUniquePK(ConstPayload(ns_.payloadType_, pv), ctx.inTransaction, ctx.rdxContext); } rollBack.Disable(); - ns_.markUpdated(false); + ns_.markUpdated(IndexOptimization::Partial); return rollBackIndexData_.IsPkModified(); } -void ItemModifier::modifyCJSON(IdType id, FieldData &field, VariantArray &values, h_vector &replUpdates, +void ItemModifier::modifyCJSON(IdType id, FieldData &field, VariantArray &values, h_vector &replUpdates, const NsContext &ctx) { PayloadValue &plData = ns_.items_[id]; const PayloadTypeImpl &pti(*ns_.payloadType_.get()); @@ -299,10 +333,10 @@ void ItemModifier::modifyCJSON(IdType id, FieldData &field, VariantArray &values } ItemImpl itemimpl(ns_.payloadType_, plData, ns_.tagsMatcher_); - itemimpl.ModifyField(field.tagspath(), values, field.details().Mode()); + itemimpl.ModifyField(field.Tagspath(), values, field.Details().Mode()); Item item = ns_.newItem(); - Error err = item.FromCJSON(itemimpl.GetCJSON(true)); + Error err = item.Unsafe().FromCJSON(itemimpl.GetCJSON(true)); if (!err.ok()) { pl.Set(0, cjsonKref); throw err; @@ -381,61 +415,44 @@ void ItemModifier::modifyCJSON(IdType id, FieldData &field, VariantArray &values impl->RealValue() = plData; } -void ItemModifier::deleteDataFromComposite(IdType itemId, FieldData &field, h_vector &needUpdateCompIndexes) { +void ItemModifier::deleteItemFromComposite(IdType itemId) { auto strHolder = ns_.strHolder(); auto indexesCacheCleaner{ns_.GetIndexesCacheCleaner()}; const auto firstCompositePos = ns_.indexes_.firstCompositePos(); const auto totalIndexes = ns_.indexes_.totalSize(); for (int i = firstCompositePos; i < totalIndexes; ++i) { - auto &compositeIdx = ns_.indexes_[i]; - const auto &fields = compositeIdx->Fields(); - const auto idxId = i - firstCompositePos; - if (needUpdateCompIndexes[idxId]) { - continue; - } - if (field.isIndex()) { - for (const auto f : fields) { - if (f == IndexValueType::SetByJsonPath) continue; - if (f == field.index()) { - needUpdateCompIndexes[idxId] = true; - break; - } - } - } - if (!needUpdateCompIndexes[idxId]) { - for (size_t tp = 0, end = fields.getTagsPathsLength(); tp < end; ++tp) { - if (field.tagspath().Compare(fields.getTagsPath(tp))) { - needUpdateCompIndexes[idxId] = true; - break; - } + if (affectedComposites_[i - firstCompositePos]) { + bool needClearCache{false}; + const auto &compositeIdx = ns_.indexes_[i]; + rollBackIndexData_.IndexAndCJsonChanged(i, compositeIdx->Opts().IsPK()); + compositeIdx->Delete(Variant(ns_.items_[itemId]), itemId, *strHolder, needClearCache); + if (needClearCache && compositeIdx->IsOrdered()) { + indexesCacheCleaner.Add(compositeIdx->SortId()); } - if (!needUpdateCompIndexes[idxId]) continue; } - bool needClearCache{false}; - rollBackIndexData_.IndexAndCJsonChanged(i, compositeIdx->Opts().IsPK()); - compositeIdx->Delete(Variant(ns_.items_[itemId]), itemId, *strHolder, needClearCache); - if (needClearCache && compositeIdx->IsOrdered()) indexesCacheCleaner.Add(compositeIdx->SortId()); } } -void ItemModifier::insertItemIntoCompositeIndexes(IdType itemId, int firstCompositePos, int totalIndexes, - const h_vector &needUpdateCompIndexes) { +void ItemModifier::insertItemIntoComposite(IdType itemId) { + const auto totalIndexes = ns_.indexes_.totalSize(); + const auto firstCompositePos = ns_.indexes_.firstCompositePos(); for (int i = firstCompositePos; i < totalIndexes; ++i) { - if (!needUpdateCompIndexes[i - firstCompositePos]) continue; - bool needClearCache{false}; - auto &compositeIdx = ns_.indexes_[i]; - rollBackIndexData_.IndexChanged(i, compositeIdx->Opts().IsPK()); - compositeIdx->Upsert(Variant(ns_.items_[itemId]), itemId, needClearCache); - if (needClearCache && compositeIdx->IsOrdered()) { - ns_.GetIndexesCacheCleaner().Add(compositeIdx->SortId()); + if (affectedComposites_[i - firstCompositePos]) { + bool needClearCache{false}; + auto &compositeIdx = ns_.indexes_[i]; + rollBackIndexData_.IndexChanged(i, compositeIdx->Opts().IsPK()); + compositeIdx->Upsert(Variant(ns_.items_[itemId]), itemId, needClearCache); + if (needClearCache && compositeIdx->IsOrdered()) { + ns_.GetIndexesCacheCleaner().Add(compositeIdx->SortId()); + } } } } void ItemModifier::modifyField(IdType itemId, FieldData &field, Payload &pl, VariantArray &values) { - assertrx_throw(field.isIndex()); - Index &index = *(ns_.indexes_[field.index()]); - if (!index.Opts().IsSparse() && field.details().Mode() == FieldModeDrop /*&& + assertrx_throw(field.IsIndex()); + Index &index = *(ns_.indexes_[field.Index()]); + if (!index.Opts().IsSparse() && field.Details().Mode() == FieldModeDrop /*&& !(field.arrayIndex() != IndexValueType::NotSet || field.tagspath().back().IsArrayNode())*/) { // TODO #1218 allow to drop array fields throw Error(errLogic, "It's only possible to drop sparse or non-index fields via UPDATE statement!"); } @@ -452,7 +469,7 @@ void ItemModifier::modifyField(IdType itemId, FieldData &field, Payload &pl, Var auto strHolder = ns_.strHolder(); auto indexesCacheCleaner{ns_.GetIndexesCacheCleaner()}; - if (field.isIndex()) { + if (field.IsIndex()) { modifyIndexValues(itemId, field, values, pl); } @@ -466,7 +483,7 @@ void ItemModifier::modifyField(IdType itemId, FieldData &field, Payload &pl, Var Variant tupleValue; std::exception_ptr exception; try { - item.ModifyField(field.tagspathWithLastIndex(), values, field.details().Mode()); + item.ModifyField(field.TagspathWithLastIndex(), values, field.Details().Mode()); } catch (...) { exception = std::current_exception(); } @@ -480,15 +497,15 @@ void ItemModifier::modifyField(IdType itemId, FieldData &field, Payload &pl, Var } void ItemModifier::modifyIndexValues(IdType itemId, const FieldData &field, VariantArray &values, Payload &pl) { - Index &index = *(ns_.indexes_[field.index()]); + Index &index = *(ns_.indexes_[field.Index()]); if (values.IsNullValue() && !index.Opts().IsArray()) { throw Error(errParams, "Non-array index fields cannot be set to null!"); } auto strHolder = ns_.strHolder(); auto indexesCacheCleaner{ns_.GetIndexesCacheCleaner()}; - bool updateArrayPart = field.arrayIndex() >= 0; + bool updateArrayPart = field.ArrayIndex() >= 0; bool isForAllItems = false; - for (const auto &tag : field.tagspath()) { + for (const auto &tag : field.Tagspath()) { if (tag.IsArrayNode()) { updateArrayPart = true; } @@ -511,8 +528,8 @@ void ItemModifier::modifyIndexValues(IdType itemId, const FieldData &field, Vari throw Error(errParams, "Cannot update array item with an empty value"); // TODO #1218 maybe delete this } int offset = -1, length = -1; - bool isForAllItems = false; - for (const auto &tag : field.tagspath()) { // TODO: Move to FieldEntry? + isForAllItems = false; + for (const auto &tag : field.Tagspath()) { // TODO: Move to FieldEntry? if (tag.IsForAllItems()) { isForAllItems = true; continue; @@ -522,9 +539,9 @@ void ItemModifier::modifyIndexValues(IdType itemId, const FieldData &field, Vari } } - ns_.skrefs = pl.GetIndexedArrayData(field.tagspathWithLastIndex(), field.index(), offset, length); + ns_.skrefs = pl.GetIndexedArrayData(field.TagspathWithLastIndex(), field.Index(), offset, length); if (offset < 0 || length < 0) { - const auto &path = field.tagspathWithLastIndex(); + const auto &path = field.TagspathWithLastIndex(); std::string indexesStr; for (auto &p : path) { if (p.Index() >= 0) { @@ -536,48 +553,48 @@ void ItemModifier::modifyIndexValues(IdType itemId, const FieldData &field, Vari } throw Error(errParams, "Requested array's index was not found: [%s]", indexesStr); } - if (field.arrayIndex() != IndexValueType::NotSet && field.arrayIndex() >= length) { - throw Error(errLogic, "Array index is out of range: [%d/%d]", field.arrayIndex(), length); + if (field.ArrayIndex() != IndexValueType::NotSet && field.ArrayIndex() >= length) { + throw Error(errLogic, "Array index is out of range: [%d/%d]", field.ArrayIndex(), length); } if (!ns_.skrefs.empty()) { bool needClearCache{false}; - rollBackIndexData_.IndexChanged(field.index(), index.Opts().IsPK()); + rollBackIndexData_.IndexChanged(field.Index(), index.Opts().IsPK()); index.Delete(ns_.skrefs.front(), itemId, *strHolder, needClearCache); if (needClearCache && index.IsOrdered()) indexesCacheCleaner.Add(index.SortId()); } bool needClearCache{false}; - rollBackIndexData_.IndexChanged(field.index(), index.Opts().IsPK()); + rollBackIndexData_.IndexChanged(field.Index(), index.Opts().IsPK()); index.Upsert(ns_.krefs, values, itemId, needClearCache); if (needClearCache && index.IsOrdered()) indexesCacheCleaner.Add(index.SortId()); if (isForAllItems) { for (int i = offset, end = offset + length; i < end; ++i) { - pl.Set(field.index(), i, ns_.krefs.front()); + pl.Set(field.Index(), i, ns_.krefs.front()); } - } else if (field.arrayIndex() == IndexValueType::NotSet) { + } else if (field.ArrayIndex() == IndexValueType::NotSet) { // Array may be resized VariantArray v; - pl.Get(field.index(), v); + pl.Get(field.Index(), v); v.erase(v.begin() + offset, v.begin() + offset + length); v.insert(v.begin() + offset, ns_.krefs.begin(), ns_.krefs.end()); - pl.Set(field.index(), v); + pl.Set(field.Index(), v); } else { // Exactly one value was changed - pl.Set(field.index(), offset, ns_.krefs.front()); + pl.Set(field.Index(), offset, ns_.krefs.front()); } } else { if (index.Opts().IsSparse()) { - pl.GetByJsonPath(field.tagspathWithLastIndex(), ns_.skrefs, index.KeyType()); + pl.GetByJsonPath(field.TagspathWithLastIndex(), ns_.skrefs, index.KeyType()); } else { - pl.Get(field.index(), ns_.skrefs, Variant::hold_t{}); + pl.Get(field.Index(), ns_.skrefs, Variant::hold_t{}); } // Required when updating index array field with several tagpaths VariantArray concatValues; int offset = -1, length = -1; - pl.GetIndexedArrayData(field.tagspathWithLastIndex(), field.index(), offset, length); + pl.GetIndexedArrayData(field.TagspathWithLastIndex(), field.Index(), offset, length); const bool kConcatIndexValues = index.Opts().IsArray() && !updateArrayPart && (length < int(ns_.skrefs.size())); // (length < int(ns_.skrefs.size()) - condition to avoid coping @@ -595,17 +612,17 @@ void ItemModifier::modifyIndexValues(IdType itemId, const FieldData &field, Vari if (!ns_.skrefs.empty()) { bool needClearCache{false}; - rollBackIndexData_.IndexChanged(field.index(), index.Opts().IsPK()); + rollBackIndexData_.IndexChanged(field.Index(), index.Opts().IsPK()); index.Delete(ns_.skrefs, itemId, *strHolder, needClearCache); if (needClearCache && index.IsOrdered()) indexesCacheCleaner.Add(index.SortId()); } bool needClearCache{false}; - rollBackIndexData_.IndexChanged(field.index(), index.Opts().IsPK()); + rollBackIndexData_.IndexChanged(field.Index(), index.Opts().IsPK()); index.Upsert(ns_.krefs, kConcatIndexValues ? concatValues : values, itemId, needClearCache); if (needClearCache && index.IsOrdered()) indexesCacheCleaner.Add(index.SortId()); if (!index.Opts().IsSparse()) { - pl.Set(field.index(), ns_.krefs); + pl.Set(field.Index(), ns_.krefs); } } } diff --git a/cpp_src/core/itemmodifier.h b/cpp_src/core/itemmodifier.h index e5a4b2193..9a5929487 100644 --- a/cpp_src/core/itemmodifier.h +++ b/cpp_src/core/itemmodifier.h @@ -1,7 +1,7 @@ #pragma once #include -#include "cluster/updaterecord.h" +#include "updates/updaterecord.h" #include "core/keyvalue/p_string.h" #include "core/payload/payloadiface.h" @@ -13,31 +13,34 @@ class UpdateEntry; class ItemModifier { public: - ItemModifier(const std::vector &, NamespaceImpl &ns, h_vector &replUpdates, + ItemModifier(const std::vector &, NamespaceImpl &ns, h_vector &replUpdates, const NsContext &ctx); ItemModifier(const ItemModifier &) = delete; ItemModifier &operator=(const ItemModifier &) = delete; ItemModifier(ItemModifier &&) = delete; ItemModifier &operator=(ItemModifier &&) = delete; - [[nodiscard]] bool Modify(IdType itemId, const NsContext &ctx, h_vector &pendedRepl); + [[nodiscard]] bool Modify(IdType itemId, const NsContext &ctx, h_vector &pendedRepl); PayloadValue &GetPayloadValueBackup() { return rollBackIndexData_.GetPayloadValueBackup(); } private: - struct FieldData { - FieldData(const UpdateEntry &entry, NamespaceImpl &ns); - void updateTagsPath(TagsMatcher &tm, const IndexExpressionEvaluator &ev); - const UpdateEntry &details() const noexcept { return entry_; } - const IndexedTagsPath &tagspath() const noexcept { return tagsPath_; } - const IndexedTagsPath &tagspathWithLastIndex() const noexcept { + using CompositeFlags = h_vector; + class FieldData { + public: + FieldData(const UpdateEntry &entry, NamespaceImpl &ns, CompositeFlags &affectedComposites); + const UpdateEntry &Details() const noexcept { return entry_; } + const IndexedTagsPath &Tagspath() const noexcept { return tagsPath_; } + const IndexedTagsPath &TagspathWithLastIndex() const noexcept { return tagsPathWithLastIndex_ ? *tagsPathWithLastIndex_ : tagsPath_; } - int arrayIndex() const noexcept { return arrayIndex_; } - int index() const noexcept { return fieldIndex_; } - bool isIndex() const noexcept { return isIndex_; } - const std::string &name() const noexcept; + int ArrayIndex() const noexcept { return arrayIndex_; } + int Index() const noexcept { return fieldIndex_; } + bool IsIndex() const noexcept { return isIndex_; } + std::string_view Name() const noexcept; private: + void appendAffectedIndexes(const NamespaceImpl &ns, CompositeFlags &affectedComposites) const; + const UpdateEntry &entry_; IndexedTagsPath tagsPath_; std::optional tagsPathWithLastIndex_; @@ -65,13 +68,12 @@ class ItemModifier { }; void modifyField(IdType itemId, FieldData &field, Payload &pl, VariantArray &values); - void modifyCJSON(IdType itemId, FieldData &field, VariantArray &values, h_vector &pendedRepl, + void modifyCJSON(IdType itemId, FieldData &field, VariantArray &values, h_vector &pendedRepl, const NsContext &); void modifyIndexValues(IdType itemId, const FieldData &field, VariantArray &values, Payload &pl); - void deleteDataFromComposite(IdType itemId, FieldData &field, h_vector &needUpdateCompIndexes); - void insertItemIntoCompositeIndexes(IdType itemId, int firstCompositePos, int totalIndexes, - const h_vector &needUpdateCompIndexes); + void deleteItemFromComposite(IdType itemId); + void insertItemIntoComposite(IdType itemId); NamespaceImpl &ns_; const std::vector &updateEntries_; @@ -113,6 +115,7 @@ class ItemModifier { }; IndexRollBack rollBackIndexData_; + CompositeFlags affectedComposites_; }; } // namespace reindexer diff --git a/cpp_src/core/key_value_type.cc b/cpp_src/core/key_value_type.cc new file mode 100644 index 000000000..587c9e9a3 --- /dev/null +++ b/cpp_src/core/key_value_type.cc @@ -0,0 +1,41 @@ +#include "key_value_type.h" + +namespace reindexer { + +std::string_view KeyValueType::Name() const noexcept { + using namespace std::string_view_literals; + switch (value_) { + case KVT::Int64: + return "int64"sv; + case KVT::Double: + return "double"sv; + case KVT::String: + return "string"sv; + case KVT::Bool: + return "bool"sv; + case KVT::Null: + return "null"sv; + case KVT::Int: + return "int"sv; + case KVT::Undefined: + return "undefined"sv; + case KVT::Composite: + return "composite"sv; + case KVT::Tuple: + return "tuple"sv; + case KVT::Uuid: + return "uuid"sv; + } + assertrx(0); + std::abort(); +} + +template +[[noreturn]] void throwKVTExceptionImpl(std::string_view msg, const T& v) { + throw Error(errParams, fmt::format("{}: '{}'", msg, v)); +} +void KeyValueType::throwKVTException(std::string_view msg, std::string_view v) { throwKVTExceptionImpl(msg, v); } +void KeyValueType::throwKVTException(std::string_view msg, int v) { throwKVTExceptionImpl(msg, v); } +void KeyValueType::throwKVTException(std::string_view msg, TagType t) { throwKVTExceptionImpl(msg, TagTypeToStr(t)); } + +} // namespace reindexer diff --git a/cpp_src/core/key_value_type.h b/cpp_src/core/key_value_type.h index b2a1b142c..6c8dfc056 100644 --- a/cpp_src/core/key_value_type.h +++ b/cpp_src/core/key_value_type.h @@ -62,7 +62,7 @@ class KeyValueType { template class VisitorWrapper { public: - explicit VisitorWrapper(Visitor& v) noexcept : visitor_{v} {} + explicit RX_ALWAYS_INLINE VisitorWrapper(Visitor& v) noexcept : visitor_{v} {} template RX_ALWAYS_INLINE auto operator()(Ts... vs) const noexcept(noexcept(std::declval()(Ts{}..., T{}))) { return visitor_(vs..., T{}); @@ -84,9 +84,9 @@ class KeyValueType { Tuple, Uuid } value_{KVT::Undefined}; - constexpr explicit KeyValueType(KVT v) noexcept : value_{v} {} + RX_ALWAYS_INLINE constexpr explicit KeyValueType(KVT v) noexcept : value_{v} {} - [[nodiscard]] static KeyValueType fromNumber(int n) { + [[nodiscard]] RX_ALWAYS_INLINE static KeyValueType fromNumber(int n) { switch (n) { case static_cast(KVT::Int64): case static_cast(KVT::Double): @@ -100,27 +100,27 @@ class KeyValueType { case static_cast(KVT::Uuid): return KeyValueType{static_cast(n)}; default: - throw Error(errParams, "Invalid int value for KeyValueType: " + std::to_string(n)); + throwKVTException("Invalid int value for KeyValueType", n); } } - [[nodiscard]] int toNumber() const noexcept { return static_cast(value_); } + [[nodiscard]] RX_ALWAYS_INLINE int toNumber() const noexcept { return static_cast(value_); } public: - constexpr KeyValueType(Int64) noexcept : value_{KVT::Int64} {} - constexpr KeyValueType(Double) noexcept : value_{KVT::Double} {} - constexpr KeyValueType(String) noexcept : value_{KVT::String} {} - constexpr KeyValueType(Bool) noexcept : value_{KVT::Bool} {} - constexpr KeyValueType(Null) noexcept : value_{KVT::Null} {} - constexpr KeyValueType(Int) noexcept : value_{KVT::Int} {} - constexpr KeyValueType(Undefined) noexcept : value_{KVT::Undefined} {} - constexpr KeyValueType(Composite) noexcept : value_{KVT::Composite} {} - constexpr KeyValueType(Tuple) noexcept : value_{KVT::Tuple} {} - constexpr KeyValueType(Uuid) noexcept : value_{KVT::Uuid} {} - constexpr KeyValueType(const KeyValueType&) noexcept = default; - constexpr KeyValueType& operator=(const KeyValueType&) noexcept = default; - constexpr KeyValueType(KeyValueType&&) noexcept = default; - constexpr KeyValueType& operator=(KeyValueType&&) noexcept = default; - explicit KeyValueType(TagType t) { + RX_ALWAYS_INLINE constexpr KeyValueType(Int64) noexcept : value_{KVT::Int64} {} + RX_ALWAYS_INLINE constexpr KeyValueType(Double) noexcept : value_{KVT::Double} {} + RX_ALWAYS_INLINE constexpr KeyValueType(String) noexcept : value_{KVT::String} {} + RX_ALWAYS_INLINE constexpr KeyValueType(Bool) noexcept : value_{KVT::Bool} {} + RX_ALWAYS_INLINE constexpr KeyValueType(Null) noexcept : value_{KVT::Null} {} + RX_ALWAYS_INLINE constexpr KeyValueType(Int) noexcept : value_{KVT::Int} {} + RX_ALWAYS_INLINE constexpr KeyValueType(Undefined) noexcept : value_{KVT::Undefined} {} + RX_ALWAYS_INLINE constexpr KeyValueType(Composite) noexcept : value_{KVT::Composite} {} + RX_ALWAYS_INLINE constexpr KeyValueType(Tuple) noexcept : value_{KVT::Tuple} {} + RX_ALWAYS_INLINE constexpr KeyValueType(Uuid) noexcept : value_{KVT::Uuid} {} + RX_ALWAYS_INLINE constexpr KeyValueType(const KeyValueType&) noexcept = default; + RX_ALWAYS_INLINE constexpr KeyValueType& operator=(const KeyValueType&) noexcept = default; + RX_ALWAYS_INLINE constexpr KeyValueType(KeyValueType&&) noexcept = default; + RX_ALWAYS_INLINE constexpr KeyValueType& operator=(KeyValueType&&) noexcept = default; + RX_ALWAYS_INLINE explicit KeyValueType(TagType t) { switch (t) { case TAG_VARINT: value_ = KVT::Int64; @@ -145,7 +145,7 @@ class KeyValueType { case TAG_END: break; } - throw Error(errParams, "Invalid tag type value for KeyValueType: " + std::string{TagTypeToStr(t)}); + throwKVTException("Invalid tag type value for KeyValueType", t); } template @@ -224,12 +224,12 @@ class KeyValueType { } template - [[nodiscard]] bool Is() const noexcept { + [[nodiscard]] RX_ALWAYS_INLINE bool Is() const noexcept { static constexpr KeyValueType v{T{}}; return v.value_ == value_; } - [[nodiscard]] bool IsSame(KeyValueType other) const noexcept { return value_ == other.value_; } - [[nodiscard]] TagType ToTagType() const noexcept { + [[nodiscard]] RX_ALWAYS_INLINE bool IsSame(KeyValueType other) const noexcept { return value_ == other.value_; } + [[nodiscard]] RX_ALWAYS_INLINE TagType ToTagType() const { switch (value_) { case KVT::Int64: case KVT::Int: @@ -241,18 +241,17 @@ class KeyValueType { case KVT::Bool: return TAG_BOOL; case KVT::Null: + case KVT::Undefined: return TAG_NULL; case KVT::Uuid: return TAG_UUID; - case KVT::Undefined: case KVT::Composite: case KVT::Tuple: break; } - assertrx(0); - std::abort(); + throwKVTException("Unexpected value type", Name()); } - [[nodiscard]] bool IsNumeric() const noexcept { + [[nodiscard]] RX_ALWAYS_INLINE bool IsNumeric() const noexcept { switch (value_) { case KVT::Int64: case KVT::Double: @@ -270,36 +269,15 @@ class KeyValueType { assertrx(0); std::abort(); } - [[nodiscard]] std::string_view Name() const noexcept { - using namespace std::string_view_literals; - switch (value_) { - case KVT::Int64: - return "int64"sv; - case KVT::Double: - return "double"sv; - case KVT::String: - return "string"sv; - case KVT::Bool: - return "bool"sv; - case KVT::Null: - return "null"sv; - case KVT::Int: - return "int"sv; - case KVT::Undefined: - return "undefined"sv; - case KVT::Composite: - return "composite"sv; - case KVT::Tuple: - return "tuple"sv; - case KVT::Uuid: - return "uuid"sv; - } - assertrx(0); - std::abort(); - } + [[nodiscard]] std::string_view Name() const noexcept; template static KeyValueType From(); + +private: + [[noreturn]] static void throwKVTException(std::string_view msg, std::string_view param); + [[noreturn]] static void throwKVTException(std::string_view msg, TagType); + [[noreturn]] static void throwKVTException(std::string_view msg, int); }; class key_string; diff --git a/cpp_src/core/keyvalue/p_string.h b/cpp_src/core/keyvalue/p_string.h index c3c1b5dd5..e00829703 100644 --- a/cpp_src/core/keyvalue/p_string.h +++ b/cpp_src/core/keyvalue/p_string.h @@ -1,6 +1,7 @@ #pragma once #include +#include "estl/span.h" #include "key_string.h" #include "tools/customhash.h" #include "tools/jsonstring.h" @@ -176,6 +177,17 @@ struct p_string { uint64_t v = 0; }; +inline span giftStr(p_string s) noexcept { +#ifndef _GLIBCXX_USE_CXX11_ABI + if (s.type() == p_string::tagCxxstr) { + // Trying to avoid COW-string problems + auto strPtr = s.getCxxstr(); + return span(const_cast(strPtr)->data(), strPtr->size()); + } +#endif // _GLIBCXX_USE_CXX11_ABI + return span(const_cast(s.data()), s.size()); +} + } // namespace reindexer namespace std { template <> diff --git a/cpp_src/core/keyvalue/uuid.h b/cpp_src/core/keyvalue/uuid.h index f2c2a4f41..01c3ad81d 100644 --- a/cpp_src/core/keyvalue/uuid.h +++ b/cpp_src/core/keyvalue/uuid.h @@ -85,7 +85,7 @@ class Uuid { assertrx(i < 2); return data_[i]; } - [[nodiscard]] static Error tryParse(std::string_view, uint64_t (&)[2]) noexcept; + static Error tryParse(std::string_view, uint64_t (&)[2]) noexcept; uint64_t data_[2]; }; diff --git a/cpp_src/core/keyvalue/variant.cc b/cpp_src/core/keyvalue/variant.cc index 425d697d6..6a552cab2 100644 --- a/cpp_src/core/keyvalue/variant.cc +++ b/cpp_src/core/keyvalue/variant.cc @@ -149,26 +149,28 @@ std::string Variant::As() const { if (isUuid()) { return std::string{Uuid{*this}}; } else { - return variant_.type.EvaluateOneOf( - [&](KeyValueType::Int) { return std::to_string(variant_.value_int); }, - [&](KeyValueType::Bool) { return variant_.value_bool ? "true"s : "false"s; }, - [&](KeyValueType::Int64) { return std::to_string(variant_.value_int64); }, - [&](KeyValueType::Double) { return double_to_str(variant_.value_double); }, - [&](KeyValueType::String) { - const auto pstr = this->operator p_string(); - if (pstr.type() == p_string::tagCxxstr || pstr.type() == p_string::tagKeyString) { - return *(pstr.getCxxstr()); - } - return pstr.toString(); - }, - [&](KeyValueType::Null) { return "null"s; }, [&](KeyValueType::Composite) { return std::string(); }, - [&](KeyValueType::Tuple) { - auto va = getCompositeValues(); - WrSerializer wrser; - va.Dump(wrser); - return std::string(wrser.Slice()); - }, - [&](KeyValueType::Uuid) { return std::string{Uuid{*this}}; }, [](KeyValueType::Undefined) -> std::string { abort(); }); + return variant_.type.EvaluateOneOf([&](KeyValueType::Int) { return std::to_string(variant_.value_int); }, + [&](KeyValueType::Bool) { return variant_.value_bool ? "true"s : "false"s; }, + [&](KeyValueType::Int64) { return std::to_string(variant_.value_int64); }, + [&](KeyValueType::Double) { return double_to_str(variant_.value_double); }, + [&](KeyValueType::String) { + const auto pstr = this->operator p_string(); + if (pstr.type() == p_string::tagCxxstr || pstr.type() == p_string::tagKeyString) { + return *(pstr.getCxxstr()); + } + return pstr.toString(); + }, + [&](KeyValueType::Null) { return "null"s; }, + [this](OneOf) -> std::string { + throw Error(errParams, "Can't convert '%s'-value to string", variant_.type.Name()); + }, + [&](KeyValueType::Tuple) { + auto va = getCompositeValues(); + WrSerializer wrser; + va.Dump(wrser); + return std::string(wrser.Slice()); + }, + [&](KeyValueType::Uuid) { return std::string{Uuid{*this}}; }); } } @@ -257,7 +259,7 @@ T parseAs(std::string_view str) { template <> int Variant::As() const { if (isUuid()) { - throw Error(errParams, "Can't convert '%s' to number", std::string{Uuid{*this}}.data()); + throw Error(errParams, "Can't convert '%s' to number", std::string{Uuid{*this}}); } return variant_.type.EvaluateOneOf( [&](KeyValueType::Bool) noexcept -> int { return variant_.value_bool; }, @@ -265,9 +267,10 @@ int Variant::As() const { [&](KeyValueType::Int64) noexcept -> int { return variant_.value_int64; }, [&](KeyValueType::Double) noexcept -> int { return variant_.value_double; }, [&](KeyValueType::String) { return parseAs(this->operator p_string()); }, - [](OneOf) noexcept { return 0; }, - [&](KeyValueType::Uuid) -> int { throw Error(errParams, "Can't convert '%s' to number", std::string{Uuid{*this}}.data()); }, - [](OneOf) noexcept -> int { abort(); }); + [this](OneOf) -> int { + throw Error(errParams, "Can't convert '%s'-value to number", Type().Name()); + }, + [&](KeyValueType::Uuid) -> int { throw Error(errParams, "Can't convert '%s' to number", std::string{Uuid{*this}}); }); } static std::optional tryConvertToBool(const p_string &str) { @@ -295,7 +298,7 @@ template <> bool Variant::As() const { using namespace std::string_view_literals; if (isUuid()) { - throw Error(errParams, "Can't convert '%s' to bool", std::string{Uuid{*this}}.data()); + throw Error(errParams, "Can't convert '%s' to bool", std::string{Uuid{*this}}); } return variant_.type.EvaluateOneOf( [&](KeyValueType::Bool) noexcept { return variant_.value_bool; }, @@ -311,15 +314,16 @@ bool Variant::As() const { throw Error(errParams, "Can't convert '%s' to bool", std::string_view(p_str)); } }, - [](OneOf) noexcept { return false; }, - [&](KeyValueType::Uuid) -> bool { throw Error(errParams, "Can't convert '%s' to bool", std::string{Uuid{*this}}.data()); }, - [](OneOf) noexcept -> bool { abort(); }); + [this](OneOf) -> bool { + throw Error(errParams, "Can't convert '%s'-value to bool", Type().Name()); + }, + [&](KeyValueType::Uuid) -> bool { throw Error(errParams, "Can't convert '%s' to bool", std::string{Uuid{*this}}); }); } template <> int64_t Variant::As() const { if (isUuid()) { - throw Error(errParams, "Can't convert '%s' to number", std::string{Uuid{*this}}.data()); + throw Error(errParams, "Can't convert '%s' to number", std::string{Uuid{*this}}); } return variant_.type.EvaluateOneOf( [&](KeyValueType::Bool) noexcept -> int64_t { return variant_.value_bool; }, @@ -327,15 +331,16 @@ int64_t Variant::As() const { [&](KeyValueType::Int64) noexcept { return variant_.value_int64; }, [&](KeyValueType::Double) noexcept -> int64_t { return variant_.value_double; }, [&](KeyValueType::String) { return parseAs(this->operator p_string()); }, - [](OneOf) noexcept -> int64_t { return 0; }, - [&](KeyValueType::Uuid) -> int64_t { throw Error(errParams, "Can't convert '%s' to number", std::string{Uuid{*this}}.data()); }, - [](OneOf) noexcept -> int64_t { abort(); }); + [this](OneOf) -> int64_t { + throw Error(errParams, "Can't convert '%s'-value to number", Type().Name()); + }, + [&](KeyValueType::Uuid) -> int64_t { throw Error(errParams, "Can't convert '%s' to number", std::string{Uuid{*this}}); }); } template <> double Variant::As() const { if (isUuid()) { - throw Error(errParams, "Can't convert '%s' to number", std::string{Uuid{*this}}.data()); + throw Error(errParams, "Can't convert '%s' to number", std::string{Uuid{*this}}); } return variant_.type.EvaluateOneOf( [&](KeyValueType::Bool) noexcept -> double { return variant_.value_bool; }, @@ -343,9 +348,10 @@ double Variant::As() const { [&](KeyValueType::Int64) noexcept -> double { return variant_.value_int64; }, [&](KeyValueType::Double) noexcept { return variant_.value_double; }, [&](KeyValueType::String) { return parseAs(this->operator p_string()); }, - [](OneOf) noexcept { return 0.0; }, - [&](KeyValueType::Uuid) -> double { throw Error(errParams, "Can't convert '%s' to number", std::string{Uuid{*this}}.data()); }, - [](OneOf) noexcept -> double { abort(); }); + [this](OneOf) -> double { + throw Error(errParams, "Can't convert '%s'-value to number", Type().Name()); + }, + [&](KeyValueType::Uuid) -> double { throw Error(errParams, "Can't convert '%s' to number", std::string{Uuid{*this}}); }); } template @@ -392,7 +398,10 @@ ComparationResult Variant::Compare(const Variant &other, const CollateOpts &coll throw Error{errParams, "Cannot compare empty values"}; } }, - [](OneOf) noexcept -> ComparationResult { abort(); }); + [](KeyValueType::Composite) -> ComparationResult { + throw Error{errParams, "Cannot compare composite variants without payload type"}; + }, + [](OneOf) noexcept -> ComparationResult { abort(); }); } } template ComparationResult Variant::Compare(const Variant &, const CollateOpts &) const; @@ -1084,6 +1093,16 @@ template void VariantArray::Dump(WrSerializer &, CheckIsStringPrintable) const; template void VariantArray::Dump(std::ostream &, CheckIsStringPrintable) const; template void VariantArray::Dump(std::stringstream &, CheckIsStringPrintable) const; +template +static std::string dumpImpl(T &&obj, CheckIsStringPrintable checkPrintableString) { + std::stringstream ss; + obj.Dump(ss, checkPrintableString); + return ss.str(); +} + +std::string Variant::Dump(CheckIsStringPrintable checkPrintableString) const { return dumpImpl(*this, checkPrintableString); } +std::string VariantArray::Dump(CheckIsStringPrintable checkPrintableString) const { return dumpImpl(*this, checkPrintableString); } + VariantArray::operator Point() const { if (size() != 2) { throw Error(errParams, "Can't convert array of %d elements to Point", size()); diff --git a/cpp_src/core/keyvalue/variant.h b/cpp_src/core/keyvalue/variant.h index 8a41b5bcc..bd88245b4 100644 --- a/cpp_src/core/keyvalue/variant.h +++ b/cpp_src/core/keyvalue/variant.h @@ -162,6 +162,7 @@ class Variant { template void Dump(T &os, CheckIsStringPrintable checkPrintableString = CheckIsStringPrintable::Yes) const; + std::string Dump(CheckIsStringPrintable checkPrintableString = CheckIsStringPrintable::Yes) const; class Less { public: @@ -305,6 +306,7 @@ class VariantArray : public h_vector { KeyValueType ArrayType() const noexcept { return empty() ? KeyValueType::Null{} : front().Type(); } template void Dump(T &os, CheckIsStringPrintable checkPrintableString = CheckIsStringPrintable::Yes) const; + std::string Dump(CheckIsStringPrintable checkPrintableString = CheckIsStringPrintable::Yes) const; template ComparationResult RelaxCompare(const VariantArray &other, const CollateOpts & = CollateOpts{}) const; void EnsureHold() { diff --git a/cpp_src/core/namespace/asyncstorage.cc b/cpp_src/core/namespace/asyncstorage.cc index 3e4d29e5f..c57c27a87 100644 --- a/cpp_src/core/namespace/asyncstorage.cc +++ b/cpp_src/core/namespace/asyncstorage.cc @@ -27,8 +27,7 @@ AsyncStorage::AsyncStorage(const AsyncStorage& o, AsyncStorage::FullLockT& stora // Do not copying lastFlushError_ and reopenTs_, because copied storage does not performs actual writes } -Error AsyncStorage::Open(datastorage::StorageType storageType, const std::string& nsName, const std::string& path, - const StorageOpts& opts) { +Error AsyncStorage::Open(datastorage::StorageType storageType, std::string_view nsName, const std::string& path, const StorageOpts& opts) { auto lck = FullLock(); throwOnStorageCopy(); diff --git a/cpp_src/core/namespace/asyncstorage.h b/cpp_src/core/namespace/asyncstorage.h index 0cdf7e708..b296529e0 100644 --- a/cpp_src/core/namespace/asyncstorage.h +++ b/cpp_src/core/namespace/asyncstorage.h @@ -99,7 +99,7 @@ class AsyncStorage { AsyncStorage() = default; AsyncStorage(const AsyncStorage& o, AsyncStorage::FullLockT& storageLock); - Error Open(datastorage::StorageType storageType, const std::string& nsName, const std::string& path, const StorageOpts& opts); + Error Open(datastorage::StorageType storageType, std::string_view nsName, const std::string& path, const StorageOpts& opts); void Destroy(); Cursor GetCursor(StorageOpts& opts); ConstCursor GetCursor(StorageOpts& opts) const; diff --git a/cpp_src/core/namespace/itemsloader.cc b/cpp_src/core/namespace/itemsloader.cc index 58d02172c..42b1b52e3 100644 --- a/cpp_src/core/namespace/itemsloader.cc +++ b/cpp_src/core/namespace/itemsloader.cc @@ -116,7 +116,6 @@ void ItemsLoader::reading() { lck.lock(); const bool wasEmpty = items_.HasNoWrittenItems(); items_.WritePlaced(); - lck.unlock(); if (wasEmpty) { cv_.notify_all(); @@ -272,7 +271,7 @@ void IndexInserters::Stop() { if (threads_.size()) { std::lock_guard lck(mtx_); shared_.terminate = true; - cv_.notify_all(); + cvReady_.notify_all(); } for (auto &th : threads_) { th.join(); @@ -281,62 +280,47 @@ void IndexInserters::Stop() { } void IndexInserters::AwaitIndexesBuild() { - if (readyThreads_.load(std::memory_order_acquire) != threads_.size()) { - std::unique_lock lck(mtx_); - cv_.wait(lck, [this] { return readyThreads_.load(std::memory_order_acquire) == threads_.size(); }); - if (!status_.ok()) { - throw status_; - } - assertrx(shared_.threadsWithNewData.empty()); + std::unique_lock lck(mtx_); + cvDone_.wait(lck, [this] { return readyThreads_ == threads_.size(); }); + if (!status_.ok()) { + throw status_; } } void IndexInserters::BuildSimpleIndexesAsync(unsigned startId, span newItems, span nsItems) { - { - std::lock_guard lck(mtx_); - shared_.newItems = newItems; - shared_.nsItems = nsItems; - shared_.startId = startId; - assertrx(shared_.threadsWithNewData.empty()); - for (unsigned tid = 0; tid < threads_.size(); ++tid) { - shared_.threadsWithNewData.emplace_back(tid + kTIDOffset); - } - shared_.composite = false; - readyThreads_.store(0, std::memory_order_relaxed); - } - cv_.notify_all(); + std::lock_guard lck(mtx_); + shared_.newItems = newItems; + shared_.nsItems = nsItems; + shared_.startId = startId; + shared_.composite = false; + readyThreads_ = 0; + ++iteration_; + cvReady_.notify_all(); } void IndexInserters::BuildCompositeIndexesAsync() { - { - std::lock_guard lck(mtx_); - assertrx(shared_.threadsWithNewData.empty()); - for (unsigned tid = 0; tid < threads_.size(); ++tid) { - shared_.threadsWithNewData.emplace_back(tid + kTIDOffset); - } - shared_.composite = true; - readyThreads_.store(0, std::memory_order_relaxed); - } - cv_.notify_all(); + std::lock_guard lck(mtx_); + shared_.composite = true; + readyThreads_ = 0; + ++iteration_; + cvReady_.notify_all(); } void IndexInserters::insertionLoop(unsigned threadId) noexcept { VariantArray krefs, skrefs; const unsigned firstCompositeIndex = indexes_.firstCompositePos(); const unsigned totalIndexes = indexes_.totalSize(); + unsigned thisLoopIteration{0}; while (true) { try { std::unique_lock lck(mtx_); - cv_.wait(lck, [this, threadId] { - return shared_.terminate || std::find(shared_.threadsWithNewData.begin(), shared_.threadsWithNewData.end(), threadId) != - shared_.threadsWithNewData.end(); - }); + cvReady_.wait(lck, [this, thisLoopIteration] { return shared_.terminate || iteration_ > thisLoopIteration; }); if (shared_.terminate) { return; } - shared_.threadsWithNewData.erase(std::find(shared_.threadsWithNewData.begin(), shared_.threadsWithNewData.end(), threadId)); lck.unlock(); + ++thisLoopIteration; const unsigned startId = shared_.startId; const unsigned threadsCnt = threads_.size(); @@ -344,7 +328,7 @@ void IndexInserters::insertionLoop(unsigned threadId) noexcept { if (shared_.composite) { for (unsigned i = 0; i < shared_.newItems.size(); ++i) { const auto id = startId + i; - auto &plData = shared_.nsItems[i]; + const auto &plData = shared_.nsItems[i]; for (unsigned field = firstCompositeIndex + threadId - kTIDOffset; field < totalIndexes; field += threadsCnt) { bool needClearCache{false}; indexes_[field]->Upsert(Variant{plData}, id, needClearCache); @@ -358,7 +342,7 @@ void IndexInserters::insertionLoop(unsigned threadId) noexcept { auto &plData = shared_.nsItems[i]; Payload pl(pt_, plData); Payload plNew = item.GetPayload(); - for (unsigned field = threadId; field < firstCompositeIndex; field += threadsCnt) { + for (unsigned field = threadId - kTIDOffset + 1; field < firstCompositeIndex; field += threadsCnt) { ItemsLoader::doInsertField(indexes_, field, id, pl, plNew, krefs, skrefs, plArrayMtxs_[id % plArrayMtxs_.size()]); } @@ -371,7 +355,7 @@ void IndexInserters::insertionLoop(unsigned threadId) noexcept { auto &plData = shared_.nsItems[i]; Payload pl(pt_, plData); Payload plNew = item.GetPayload(); - for (unsigned field = threadId; field < firstCompositeIndex; field += threadsCnt) { + for (unsigned field = threadId - kTIDOffset + 1; field < firstCompositeIndex; field += threadsCnt) { ItemsLoader::doInsertField(indexes_, field, id, pl, plNew, krefs, skrefs, dummyMtx); } } diff --git a/cpp_src/core/namespace/itemsloader.h b/cpp_src/core/namespace/itemsloader.h index 3d39c27c8..f7bf79cf3 100644 --- a/cpp_src/core/namespace/itemsloader.h +++ b/cpp_src/core/namespace/itemsloader.h @@ -125,32 +125,33 @@ class IndexInserters { span newItems; span nsItems; unsigned startId = 0; - h_vector threadsWithNewData; bool terminate = false; bool composite = false; }; void insertionLoop(unsigned threadId) noexcept; void onItemsHandled() noexcept { - if ((readyThreads_.fetch_add(1, std::memory_order_acq_rel) + 1) == threads_.size()) { - std::lock_guard lck(mtx_); - cv_.notify_all(); + std::lock_guard lck(mtx_); + if (++readyThreads_ == threads_.size()) { + cvDone_.notify_one(); } } void onException(Error e) { std::lock_guard lck(mtx_); status_ = std::move(e); - if ((readyThreads_.fetch_add(1, std::memory_order_acq_rel) + 1) == threads_.size()) { - cv_.notify_all(); + if (++readyThreads_ == threads_.size()) { + cvDone_.notify_one(); } } std::mutex mtx_; - std::condition_variable cv_; + std::condition_variable cvReady_; + std::condition_variable cvDone_; + unsigned iteration_{0}; NamespaceImpl::IndexesStorage& indexes_; const PayloadType pt_; SharedData shared_; - std::atomic readyThreads_ = {0}; + unsigned readyThreads_ = {0}; std::vector threads_; Error status_; bool hasArrayIndexes_ = false; diff --git a/cpp_src/core/namespace/namespace.cc b/cpp_src/core/namespace/namespace.cc index 904fe6134..ecd68adfc 100644 --- a/cpp_src/core/namespace/namespace.cc +++ b/cpp_src/core/namespace/namespace.cc @@ -25,7 +25,9 @@ void Namespace::CommitTransaction(LocalTransaction& tx, LocalQueryResults& resul auto clonerLck = statCalculator.CreateLock(clonerMtx_, ctx.rdxContext); nsl = ns_; - if (needNamespaceCopy(nsl, tx)) { + if (needNamespaceCopy(nsl, tx) && + (tx.GetSteps().size() >= static_cast(txSizeToAlwaysCopy_.load(std::memory_order_relaxed)) || + isExpectingSelectsOnNamespace(nsl, ctx))) { PerfStatCalculatorMT nsCopyCalc(copyStatsCounter_, enablePerfCounters); calc.SetCounter(nsl->updatePerfCounter_); calc.LockHit(); @@ -61,8 +63,8 @@ void Namespace::CommitTransaction(LocalTransaction& tx, LocalQueryResults& resul hasCopy_.store(false, std::memory_order_release); if (!nsl->repl_.temporary && !nsCtx.inSnapshot) { // If commit happens in ns copy, than the copier have to handle replication - auto err = ns_->clusterizator_.Replicate( - cluster::UpdateRecord{cluster::UpdateRecord::Type::CommitTx, ns_->name_, ns_->wal_.LastLSN(), ns_->repl_.nsVersion, + auto err = ns_->observers_.SendUpdate( + updates::UpdateRecord{updates::URType::CommitTx, ns_->name_, ns_->wal_.LastLSN(), ns_->repl_.nsVersion, ctx.rdxContext.EmmiterServerId()}, [&clonerLck, &storageLock, &nsRlck]() { storageLock.unlock(); @@ -136,7 +138,23 @@ bool Namespace::needNamespaceCopy(const NamespaceImpl::Ptr& ns, const LocalTrans (stepsCount >= txSizeToAlwaysCopy); } -void Namespace::doRename(const Namespace::Ptr& dst, const std::string& newName, const std::string& storagePath, +bool Namespace::isExpectingSelectsOnNamespace(const NamespaceImpl::Ptr& ns, const NsContext& ctx) { + // Some kind of heuristic: if there were no selects on this namespace yet and no one awaits read lock for it, probably we do not have to + // copy it. Improves scenarios, when user wants to fill namespace before any selections. + // It would be more optimal to acquire lock here and pass it further to the transaction, but this case is rare, so trying to not make it + // complicated. + if (ns->hadSelects() || !ns->isNotLocked(ctx.rdxContext)) { + return true; + } + std::this_thread::yield(); + if (!ns->hadSelects()) { + const bool enableTxHeuristic = !std::getenv("REINDEXER_NOTXHEURISTIC"); + return enableTxHeuristic; + } + return false; +} + +void Namespace::doRename(const Namespace::Ptr& dst, std::string_view newName, const std::string& storagePath, const std::function)>& replicateCb, const RdxContext& ctx) { logPrintf(LogTrace, "[rename] Trying to rename namespace '%s'...", GetName(ctx)); std::string dbpath; @@ -167,7 +185,7 @@ void Namespace::doRename(const Namespace::Ptr& dst, const std::string& newName, } dstNs->checkClusterRole(ctx); dbpath = dstNs->storage_.GetPath(); - } else if (newName == srcNs.name_) { + } else if (newName == srcNs.name_.OriginalName()) { return; } srcNs.checkClusterRole(ctx); @@ -191,7 +209,10 @@ void Namespace::doRename(const Namespace::Ptr& dst, const std::string& newName, assertrx(dstLck.owns_lock()); dstLck.unlock(); } - srcNs.storage_.Open(storageType, srcNs.name_, srcDbpath, srcNs.storageOpts_); + auto err = srcNs.storage_.Open(storageType, srcNs.name_, srcDbpath, srcNs.storageOpts_); + if (!err.ok()) { + logPrintf(LogError, "Unable to reopen storage after unsuccesfull renaming: %s", err.what()); + } throw Error(errParams, "Unable to rename '%s' to '%s'", srcDbpath, dbpath); } } @@ -208,9 +229,12 @@ void Namespace::doRename(const Namespace::Ptr& dst, const std::string& newName, dstLck.unlock(); } else { logPrintf(LogInfo, "[rename] Rename namespace '%s' to '%s'", srcNs.name_, newName); - srcNs.name_ = newName; + srcNs.name_ = NamespaceName(newName); } - srcNs.payloadType_.SetName(srcNs.name_); + srcNs.payloadType_.SetName(srcNs.name_.OriginalName()); + srcNs.tagsMatcher_.UpdatePayloadType(srcNs.payloadType_, NeedChangeTmVersion::No); + logPrintf(LogInfo, "[tm:%s]:%d: Rename done. TagsMatcher: { state_token: %08X, version: %d }", srcNs.name_, srcNs.wal_.GetServer(), + srcNs.tagsMatcher_.stateToken(), srcNs.tagsMatcher_.version()); if (hadStorage) { logPrintf(LogTrace, "[rename] Storage was moved from %s to %s", srcDbpath, dbpath); diff --git a/cpp_src/core/namespace/namespace.h b/cpp_src/core/namespace/namespace.h index 7356a5e0e..ebca594bb 100644 --- a/cpp_src/core/namespace/namespace.h +++ b/cpp_src/core/namespace/namespace.h @@ -87,18 +87,12 @@ class Namespace { public: using Ptr = shared_ptr; -#ifdef REINDEX_WITH_V3_FOLLOWERS - Namespace(const std::string &name, std::optional stateToken, cluster::INsDataReplicator &clusterizator, + Namespace(const std::string &name, std::optional stateToken, cluster::IDataSyncer &clusterizator, BackgroundNamespaceDeleter &bgDeleter, UpdatesObservers &observers) : ns_(make_intrusive(name, std::move(stateToken), clusterizator, observers)), bgDeleter_(bgDeleter) {} -#else // REINDEX_WITH_V3_FOLLOWERS - Namespace(const std::string &name, std::optional stateToken, cluster::INsDataReplicator &clusterizator, - BackgroundNamespaceDeleter &bgDeleter) - : ns_(make_intrusive(name, std::move(stateToken), clusterizator)), bgDeleter_(bgDeleter) {} -#endif // REINDEX_WITH_V3_FOLLOWERS void CommitTransaction(LocalTransaction &tx, LocalQueryResults &result, const NsContext &ctx); - std::string GetName(const RdxContext &ctx) const { return nsFuncWrapper<&NamespaceImpl::GetName>(ctx); } + NamespaceName GetName(const RdxContext &ctx) const { return nsFuncWrapper<&NamespaceImpl::GetName>(ctx); } bool IsSystem(const RdxContext &ctx) const { return nsFuncWrapper<&NamespaceImpl::IsSystem>(ctx); } bool IsTemporary(const RdxContext &ctx) const { return nsFuncWrapper<&NamespaceImpl::IsTemporary>(ctx); } void SetNsVersion(lsn_t version, const RdxContext &ctx) { nsFuncWrapper<&NamespaceImpl::SetNsVersion>(version, ctx); } @@ -252,7 +246,8 @@ class Namespace { private: bool needNamespaceCopy(const NamespaceImpl::Ptr &ns, const LocalTransaction &tx) const noexcept; - void doRename(const Namespace::Ptr &dst, const std::string &newName, const std::string &storagePath, + bool isExpectingSelectsOnNamespace(const NamespaceImpl::Ptr &ns, const NsContext& ctx); + void doRename(const Namespace::Ptr &dst, std::string_view newName, const std::string &storagePath, const std::function)> &replicateCb, const RdxContext &ctx); NamespaceImpl::Ptr atomicLoadMainNs() const { std::lock_guard lck(nsPtrSpinlock_); @@ -265,10 +260,10 @@ class Namespace { NamespaceImpl::Ptr ns_; std::unique_ptr nsCopy_; - std::atomic hasCopy_ = {false}; using Mutex = MarkedMutex; mutable Mutex clonerMtx_; mutable spinlock nsPtrSpinlock_; + std::atomic hasCopy_ = {false}; std::atomic startCopyPolicyTxSize_; std::atomic copyPolicyMultiplier_; std::atomic txSizeToAlwaysCopy_; diff --git a/cpp_src/core/namespace/namespaceimpl.cc b/cpp_src/core/namespace/namespaceimpl.cc index 6b7f4545a..1efd3bcc1 100644 --- a/cpp_src/core/namespace/namespaceimpl.cc +++ b/cpp_src/core/namespace/namespaceimpl.cc @@ -4,6 +4,7 @@ #include #include #include "core/cjson/cjsondecoder.h" +#include "core/cjson/defaultvaluecoder.h" #include "core/cjson/jsonbuilder.h" #include "core/cjson/uuid_recoders.h" #include "core/index/index.h" @@ -27,13 +28,9 @@ #include "tools/timetools.h" #include "wal/walselecter.h" -#ifdef REINDEX_WITH_V3_FOLLOWERS -#include "replv3/updatesobserver.h" -#endif // REINDEX_WITH_V3_FOLLOWERS - using std::chrono::duration_cast; using std::chrono::microseconds; -using reindexer::cluster::UpdateRecord; +using reindexer::updates::UpdateRecord; #define kStorageIndexesPrefix "indexes" #define kStorageSchemaPrefix "schema" @@ -42,7 +39,7 @@ using reindexer::cluster::UpdateRecord; #define kStorageMetaPrefix "meta" #define kTupleName "-tuple" -static const std::string kPKIndexName = "#pk"; +constexpr static std::string_view kPKIndexName = "#pk"; constexpr int kWALStatementItemsThreshold = 5; #define kStorageMagic 0x1234FEDC @@ -50,16 +47,15 @@ constexpr int kWALStatementItemsThreshold = 5; namespace reindexer { -std::atomic rxAllowNamespaceLeak = {false}; +std::atomic_bool rxAllowNamespaceLeak = {false}; constexpr int64_t kStorageSerialInitial = 1; constexpr uint8_t kSysRecordsBackupCount = 8; constexpr uint8_t kSysRecordsFirstWriteCopies = 3; constexpr size_t kMaxMemorySizeOfStringsHolder = 1ull << 24; +constexpr size_t kMaxSchemaCharsToPrint = 128; -NamespaceImpl::IndexesStorage::IndexesStorage(const NamespaceImpl& ns) : ns_(ns) {} - -void NamespaceImpl::IndexesStorage::MoveBase(IndexesStorage&& src) { Base::operator=(std::move(src)); } +NamespaceImpl::IndexesStorage::IndexesStorage(const NamespaceImpl& ns) noexcept : ns_(ns) {} // private implementation and NOT THREADSAFE of copy CTOR NamespaceImpl::NamespaceImpl(const NamespaceImpl& src, AsyncStorage::FullLockT& storageLock) @@ -79,7 +75,7 @@ NamespaceImpl::NamespaceImpl(const NamespaceImpl& src, AsyncStorage::FullLockT& krefs(src.krefs), skrefs(src.skrefs), sysRecordsVersions_{src.sysRecordsVersions_}, - locker_(src.clusterizator_, *this), + locker_(src.locker_.Syncer(), *this), schema_(src.schema_), enablePerfCounters_{src.enablePerfCounters_.load()}, config_{src.config_}, @@ -99,17 +95,12 @@ NamespaceImpl::NamespaceImpl(const NamespaceImpl& src, AsyncStorage::FullLockT& optimizationState_{NotOptimized}, strHolder_{makeStringsHolder()}, nsUpdateSortedContextMemory_{0}, - clusterizator_{src.clusterizator_}, dbDestroyed_(false), - incarnationTag_(src.incarnationTag_) -#ifdef REINDEX_WITH_V3_FOLLOWERS - , - observers_(src.observers_) -#endif // REINDEX_WITH_V3_FOLLOWERS -{ + incarnationTag_(src.incarnationTag_), + observers_(src.observers_) { for (auto& idxIt : src.indexes_) indexes_.push_back(idxIt->Clone()); - markUpdated(true); + markUpdated(IndexOptimization::Full); logPrintf(LogInfo, "Namespace::CopyContentsFrom (%s).Workers: %d, timeout: %d, tm: { state_token: 0x%08X, version: %d }", name_, config_.optimizationSortWorkers, config_.optimizationTimeout, tagsMatcher_.stateToken(), tagsMatcher_.version()); } @@ -120,18 +111,14 @@ static int64_t GetCurrentTimeUS() noexcept { return duration_cast(system_clock_w::now().time_since_epoch()).count(); } -NamespaceImpl::NamespaceImpl(const std::string& name, std::optional stateToken, cluster::INsDataReplicator& clusterizator -#ifdef REINDEX_WITH_V3_FOLLOWERS - , - UpdatesObservers& observers -#endif // REINDEX_WITH_V3_FOLLOWERS - ) +NamespaceImpl::NamespaceImpl(const std::string& name, std::optional stateToken, const cluster::IDataSyncer& syncer, + UpdatesObservers& observers) : intrusive_atomic_rc_base(), indexes_(*this), name_(name), payloadType_(name), tagsMatcher_(payloadType_, stateToken.has_value() ? stateToken.value() : tools::RandomGenerator::gets32()), - locker_(clusterizator, *this), + locker_(syncer, *this), enablePerfCounters_(false), queryCountCache_( std::make_unique(config_.cacheConfig.queryCountCacheSize, config_.cacheConfig.queryCountHitsToCache)), @@ -142,14 +129,9 @@ NamespaceImpl::NamespaceImpl(const std::string& name, std::optional sta lastUpdateTime_{0}, nsIsLoading_(false), strHolder_{makeStringsHolder()}, - clusterizator_(clusterizator), dbDestroyed_(false), - incarnationTag_(GetCurrentTimeUS() % lsn_t::kDefaultCounter, 0) -#ifdef REINDEX_WITH_V3_FOLLOWERS - , - observers_(observers) -#endif // REINDEX_WITH_V3_FOLLOWERS -{ + incarnationTag_(GetCurrentTimeUS() % lsn_t::kDefaultCounter, 0), + observers_(observers) { logPrintf(LogTrace, "NamespaceImpl::NamespaceImpl (%s)", name_); FlagGuardT nsLoadingGuard(nsIsLoading_); items_.reserve(10000); @@ -159,7 +141,6 @@ NamespaceImpl::NamespaceImpl(const std::string& name, std::optional sta // Add index and payload field for tuple of non indexed fields IndexDef tupleIndexDef(kTupleName, {}, IndexStrStore, IndexOpts()); addIndex(tupleIndexDef, false); - updateSelectTime(); logPrintf(LogInfo, "Namespace::Construct (%s).Workers: %d, timeout: %d, tm: { state_token: 0x%08X (%s), version: %d }", name_, config_.optimizationSortWorkers, config_.optimizationTimeout, tagsMatcher_.stateToken(), @@ -271,6 +252,7 @@ void NamespaceImpl::OnConfigUpdated(DBConfigProvider& configProvider, const RdxC // ! Updating storage under write lock auto wlck = simpleWLock(ctx); + configData.maxIterationsIdSetPreResult = correctMaxIterationsIdSetPreResult(configData.maxIterationsIdSetPreResult); const bool needReoptimizeIndexes = (config_.optimizationSortWorkers == 0) != (configData.optimizationSortWorkers == 0); if (config_.optimizationSortWorkers != configData.optimizationSortWorkers || config_.optimizationTimeout != configData.optimizationTimeout) { @@ -282,12 +264,12 @@ void NamespaceImpl::OnConfigUpdated(DBConfigProvider& configProvider, const RdxC const bool needReconfigureJoinCache = !config_.cacheConfig.IsJoinCacheEqual(configData.cacheConfig); const bool needReconfigureQueryCountCache = !config_.cacheConfig.IsQueryCountCacheEqual(configData.cacheConfig); config_ = configData; - storageOpts_.LazyLoad(configData.lazyLoad); - storageOpts_.noQueryIdleThresholdSec = configData.noQueryIdleThreshold; + storageOpts_.LazyLoad(config_.lazyLoad); + storageOpts_.noQueryIdleThresholdSec = config_.noQueryIdleThreshold; storage_.SetForceFlushLimit(config_.syncStorageFlushLimit); for (auto& idx : indexes_) { - idx->EnableUpdatesCountingMode(configData.idxUpdatesCountingMode); + idx->EnableUpdatesCountingMode(config_.idxUpdatesCountingMode); } if (needReconfigureIdxCache) { for (auto& idx : indexes_) { @@ -350,13 +332,13 @@ void NamespaceImpl::OnConfigUpdated(DBConfigProvider& configProvider, const RdxC } template -class NamespaceImpl::RollBack_recreateCompositeIndexes : private RollBackBase { +class NamespaceImpl::RollBack_recreateCompositeIndexes final : private RollBackBase { public: RollBack_recreateCompositeIndexes(NamespaceImpl& ns, size_t startIdx, size_t count) : ns_{ns}, startIdx_{startIdx} { indexes_.reserve(count); std::swap(ns_.indexesToComposites_, indexesToComposites_); } - ~RollBack_recreateCompositeIndexes() { + ~RollBack_recreateCompositeIndexes() override { RollBack(); for (auto& idx : indexes_) { try { @@ -434,14 +416,14 @@ NamespaceImpl::RollBack_recreateCompositeIndexes NamespaceImpl::re } template -class NamespaceImpl::RollBack_updateItems : private RollBackBase { +class NamespaceImpl::RollBack_updateItems final : private RollBackBase { public: RollBack_updateItems(NamespaceImpl& ns, RollBack_recreateCompositeIndexes&& rb, uint64_t dh, size_t ds) noexcept : ns_{ns}, rollbacker_recreateCompositeIndexes_{std::move(rb)}, dataHash_{dh}, itemsDataSize_{ds} { items_.reserve(ns_.items_.size()); } - ~RollBack_updateItems() { RollBack(); } - void Disable() noexcept { + ~RollBack_updateItems() override { RollBack(); } + void Disable() noexcept override { rollbacker_recreateCompositeIndexes_.Disable(); RollBackBase::Disable(); } @@ -481,6 +463,28 @@ class NamespaceImpl::RollBack_updateItems : private RollBackBase { std::unique_ptr tuple_; }; +std::vector NamespaceImpl::pickJsonPath(const PayloadFieldType& fld) { + const auto& paths = fld.JsonPaths(); + if (fld.IsArray()) { + std::vector result; + result.reserve(paths.size()); + for (const auto& path : paths) { + auto tags = tagsMatcher_.path2tag(path, false); + result.push_back(std::move(tags)); + // first without nested path - always (any, now last one found) + if ((result.size() > 1) && (result.back().size() == 1)) { + std::swap(result.front(), result.back()); + } + } + + return result; + } + + assertrx_throw(paths.size() == 1); + auto tags = tagsMatcher_.path2tag(paths.front(), false); + return {std::move(tags)}; +} + template <> class NamespaceImpl::RollBack_updateItems { public: @@ -497,64 +501,76 @@ class NamespaceImpl::RollBack_updateItems { RollBack_updateItems& operator=(RollBack_updateItems&&) = delete; }; -template -NamespaceImpl::RollBack_updateItems NamespaceImpl::updateItems(const PayloadType& oldPlType, const FieldsSet& changedFields, - int deltaFields) { - logPrintf(LogTrace, "Namespace::updateItems(%s) delta=%d", name_, deltaFields); +template +NamespaceImpl::RollBack_updateItems NamespaceImpl::updateItems(const PayloadType& oldPlType, int changedField) { + logPrintf(LogTrace, "Namespace::updateItems(%s) changeType=%s", name_, fieldChangeType == FieldChangeType::Add ? "Add" : "Delete"); - assertrx(oldPlType->NumFields() + deltaFields == payloadType_->NumFields()); + assertrx(oldPlType->NumFields() + int(fieldChangeType) == payloadType_->NumFields()); - const int compositeStartIdx = - (deltaFields >= 0) ? indexes_.firstCompositePos() : indexes_.firstCompositePos(oldPlType, sparseIndexesCount_); + const int compositeStartIdx = (fieldChangeType == FieldChangeType::Add) ? indexes_.firstCompositePos() + : indexes_.firstCompositePos(oldPlType, sparseIndexesCount_); const int compositeEndIdx = indexes_.totalSize(); - // All the composite indexes must be recreated, beacuse those indexes are holding pointers to the old Payloads + // all composite indexes must be recreated, because those indexes are holding pointers to old Payloads RollBack_updateItems rollbacker{*this, recreateCompositeIndexes(compositeStartIdx, compositeEndIdx), repl_.dataHash, itemsDataSize_}; - for (auto& idx : indexes_) { idx->UpdatePayloadType(PayloadType{payloadType_}); } - VariantArray skrefsDel, skrefsUps; - ItemImpl newItem(payloadType_, tagsMatcher_); - newItem.Unsafe(true); - int errCount = 0; - Error lastErr = errOK; - repl_.dataHash = 0; - itemsDataSize_ = 0; - auto indexesCacheCleaner{GetIndexesCacheCleaner()}; + // no items, work done, stop processing + if (items_.empty()) { + return rollbacker; + } + std::unique_ptr recoder; - if (deltaFields < 0) { - assertrx(deltaFields == -1); - for (auto fieldIdx : changedFields) { - const auto& fld = oldPlType.Field(fieldIdx); - if (fieldIdx != 0 && fld.Type().Is()) { - const auto& jsonPaths = fld.JsonPaths(); - assertrx(jsonPaths.size() == 1); - if (fld.IsArray()) { - recoder.reset(new RecoderUuidToString{tagsMatcher_.path2tag(jsonPaths[0])}); - } else { - recoder.reset(new RecoderUuidToString{tagsMatcher_.path2tag(jsonPaths[0])}); - } + if constexpr (fieldChangeType == FieldChangeType::Delete) { + assertrx_throw(changedField > 0); + const auto& fld = oldPlType.Field(changedField); + if (fld.Type().Is()) { + const auto& jsonPaths = fld.JsonPaths(); + assertrx(jsonPaths.size() == 1); + const auto tags = tagsMatcher_.path2tag(jsonPaths[0]); + if (fld.IsArray()) { + recoder = std::make_unique>(tags); + } else { + recoder = std::make_unique>(tags); } } - } else if (deltaFields > 0) { - assertrx(deltaFields == 1); - for (auto fieldIdx : changedFields) { - const auto& fld = payloadType_.Field(fieldIdx); - if (fieldIdx != 0 && fld.Type().Is()) { - if (fld.IsArray()) { - recoder.reset(new RecoderStringToUuidArray{fieldIdx}); - } else { - recoder.reset(new RecoderStringToUuid{fieldIdx}); + + } else { + static_assert(fieldChangeType == FieldChangeType::Add); + assertrx_throw(changedField > 0); + const auto& fld = payloadType_.Field(changedField); + if (fld.Type().Is()) { + if (fld.IsArray()) { + recoder = std::make_unique(changedField); + } else { + recoder = std::make_unique(changedField); + } + } else { + const auto& indexToUpdate = indexes_[changedField]; + if (!IsComposite(indexToUpdate->Type()) && !indexToUpdate->Opts().IsSparse()) { + auto tagsNames = pickJsonPath(fld); + if (!tagsNames.empty()) { + recoder = std::make_unique(name_, fld, std::move(tagsNames), changedField); } } } } - if (!items_.empty()) { - rollbacker.SaveTuple(); - } - for (size_t rowId = 0; rowId < items_.size(); rowId++) { + rollbacker.SaveTuple(); + + VariantArray skrefsDel, skrefsUps; + ItemImpl newItem(payloadType_, tagsMatcher_); + newItem.Unsafe(true); + repl_.dataHash = 0; + itemsDataSize_ = 0; + auto indexesCacheCleaner{GetIndexesCacheCleaner()}; + + auto& tuple = *indexes_[0]; + auto& index = *indexes_[changedField]; + + WrSerializer pk, data; + for (size_t rowId = 0; rowId < items_.size(); ++rowId) { if (items_[rowId].IsFree()) { continue; } @@ -562,46 +578,73 @@ NamespaceImpl::RollBack_updateItems NamespaceImpl::updateItems(con Payload oldValue(oldPlType, plCurr); ItemImpl oldItem(oldPlType, plCurr, tagsMatcher_); oldItem.Unsafe(true); - newItem.FromCJSON(&oldItem, recoder.get()); + newItem.FromCJSON(oldItem, recoder.get()); + const bool itemTupleUpdated = recoder && recoder->Reset(); - PayloadValue plNew = oldValue.CopyTo(payloadType_, deltaFields >= 0); + PayloadValue plNew = oldValue.CopyTo(payloadType_, fieldChangeType == FieldChangeType::Add); plNew.SetLSN(plCurr.GetLSN()); - Payload newValue(payloadType_, plNew); - for (auto fieldIdx : changedFields) { - auto& index = *indexes_[fieldIdx]; - if ((fieldIdx == 0) || deltaFields <= 0) { - oldValue.Get(fieldIdx, skrefsDel, Variant::hold_t{}); - bool needClearCache{false}; - index.Delete(skrefsDel, rowId, *strHolder_, needClearCache); - if (needClearCache && index.IsOrdered()) indexesCacheCleaner.Add(index.SortId()); - } + // update tuple + oldValue.Get(0, skrefsDel, Variant::hold_t{}); + bool needClearCache{false}; + tuple.Delete(skrefsDel, rowId, *strHolder_, needClearCache); + newItem.GetPayload().Get(0, skrefsUps); + krefs.resize(0); + tuple.Upsert(krefs, skrefsUps, rowId, needClearCache); + if (needClearCache && tuple.IsOrdered()) { + indexesCacheCleaner.Add(tuple.SortId()); + } - if ((fieldIdx == 0) || deltaFields >= 0) { - newItem.GetPayload().Get(fieldIdx, skrefsUps); - krefs.resize(0); - bool needClearCache{false}; - index.Upsert(krefs, skrefsUps, rowId, needClearCache); - if (needClearCache && index.IsOrdered()) indexesCacheCleaner.Add(index.SortId()); - newValue.Set(fieldIdx, krefs); + // update index + Payload newValue(payloadType_, plNew); + newValue.Set(0, krefs); + + if constexpr (fieldChangeType == FieldChangeType::Delete) { + oldValue.Get(changedField, skrefsDel, Variant::hold_t{}); + needClearCache = false; + index.Delete(skrefsDel, rowId, *strHolder_, needClearCache); + if (needClearCache && index.IsOrdered()) { + indexesCacheCleaner.Add(index.SortId()); } + } else { + static_assert(fieldChangeType == FieldChangeType::Add); + newItem.GetPayload().Get(changedField, skrefsUps); + krefs.resize(0); + needClearCache = false; + index.Upsert(krefs, skrefsUps, rowId, needClearCache); + if (needClearCache && index.IsOrdered()) { + indexesCacheCleaner.Add(index.SortId()); + } + newValue.Set(changedField, krefs); } for (int fieldIdx = compositeStartIdx; fieldIdx < compositeEndIdx; ++fieldIdx) { - bool needClearCache{false}; - indexes_[fieldIdx]->Upsert(Variant(plNew), rowId, needClearCache); - if (needClearCache && indexes_[fieldIdx]->IsOrdered()) indexesCacheCleaner.Add(indexes_[fieldIdx]->SortId()); + needClearCache = false; + auto& fieldIndex = *indexes_[fieldIdx]; + fieldIndex.Upsert(Variant(plNew), rowId, needClearCache); + if (needClearCache && fieldIndex.IsOrdered()) { + indexesCacheCleaner.Add(fieldIndex.SortId()); + } } rollbacker.SaveItem(rowId, std::move(plCurr)); plCurr = std::move(plNew); repl_.dataHash ^= Payload(payloadType_, plCurr).GetHash(); itemsDataSize_ += plCurr.GetCapacity() + sizeof(PayloadValue::dataHeader); + + // update data in storage + if (itemTupleUpdated && storage_.IsValid()) { + pk.Reset(); + data.Reset(); + pk << kRxStorageItemPrefix; + Payload(payloadType_, plCurr).SerializeFields(pk, pkFields()); + data.PutUInt64(int64_t(plCurr.GetLSN())); + newItem.GetCJSON(data); + storage_.Write(pk.Slice(), data.Slice()); + } } - markUpdated(false); - if (errCount != 0) { - logPrintf(LogError, "Can't update indexes of %d items in namespace %s: %s", errCount, name_, lastErr.what()); - } + + markUpdated(IndexOptimization::Partial); return rollbacker; } @@ -630,7 +673,7 @@ void NamespaceImpl::AddIndex(const IndexDef& indexDef, const RdxContext& ctx) { } else { if (ctx.HasEmmiterServer()) { // Make sure, that index was already replicated to emitter - pendedRepl.emplace_back(UpdateRecord::Type::EmptyUpdate, name_, ctx.EmmiterServerId()); + pendedRepl.emplace_back(updates::URType::EmptyUpdate, name_, ctx.EmmiterServerId()); replicate(std::move(pendedRepl), std::move(wlck), false, nullptr, ctx); } return; @@ -683,12 +726,13 @@ void NamespaceImpl::SetSchema(std::string_view schema, const RdxContext& ctx) { } if (ctx.HasEmmiterServer()) { // Make sure, that schema was already replicated to emitter - pendedRepl.emplace_back(UpdateRecord::Type::EmptyUpdate, name_, ctx.EmmiterServerId()); + pendedRepl.emplace_back(updates::URType::EmptyUpdate, name_, ctx.EmmiterServerId()); replicate(std::move(pendedRepl), std::move(wlck), false, nullptr, ctx); } return; } } + checkClusterStatus(ctx); setSchema(schema, pendedRepl, ctx); saveSchemaToStorage(); @@ -781,8 +825,7 @@ void NamespaceImpl::dropIndex(const IndexDef& index, bool disableTmVersionInc) { PayloadType oldPlType = payloadType_; payloadType_.Drop(index.name_); tagsMatcher_.UpdatePayloadType(payloadType_, disableTmVersionInc ? NeedChangeTmVersion::No : NeedChangeTmVersion::Increment); - FieldsSet changedFields{0, fieldIdx}; - auto rollbacker{updateItems(oldPlType, changedFields, -1)}; + auto rollbacker{updateItems(oldPlType, fieldIdx)}; rollbacker.Disable(); } @@ -796,7 +839,7 @@ void NamespaceImpl::doDropIndex(const IndexDef& index, UpdatesContainer& pendedR dropIndex(index, ctx.inSnapshot); addToWAL(index, WalIndexDrop, ctx); - pendedRepl.emplace_back(UpdateRecord::Type::IndexDrop, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), index); + pendedRepl.emplace_back(updates::URType::IndexDrop, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), index); } static void verifyConvertTypes(KeyValueType from, KeyValueType to, const PayloadType& payloadType, const FieldsSet& fields) { @@ -949,14 +992,14 @@ void NamespaceImpl::verifyUpdateIndex(const IndexDef& indexDef) const { } } -class NamespaceImpl::RollBack_insertIndex : private RollBackBase { +class NamespaceImpl::RollBack_insertIndex final : private RollBackBase { using IndexesNamesIt = decltype(NamespaceImpl::indexesNames_)::iterator; public: RollBack_insertIndex(NamespaceImpl& ns, NamespaceImpl::IndexesStorage::iterator idxIt, int idxNo) noexcept : ns_{ns}, insertedIndex_{idxIt}, insertedIdxNo_{idxNo} {} RollBack_insertIndex(RollBack_insertIndex&&) noexcept = default; - ~RollBack_insertIndex() { RollBack(); } + ~RollBack_insertIndex() override { RollBack(); } void RollBack() noexcept { if (IsDisabled()) return; if (insertedIdxName_) { @@ -1000,7 +1043,7 @@ class NamespaceImpl::RollBack_insertIndex : private RollBackBase { NamespaceImpl& ns_; NamespaceImpl::IndexesStorage::iterator insertedIndex_; std::optional insertedIdxName_; - int insertedIdxNo_; + int insertedIdxNo_{0}; bool pkIndexNameInserted_{false}; }; @@ -1016,22 +1059,22 @@ NamespaceImpl::RollBack_insertIndex NamespaceImpl::insertIndex(std::unique_ptr&& rb) noexcept { rollbacker_updateItems_.emplace(std::move(rb)); } - void Disable() noexcept { + void Disable() noexcept override { if (rollbacker_insertIndex_) rollbacker_insertIndex_->Disable(); if (rollbacker_updateItems_) rollbacker_updateItems_->Disable(); RollBackBase::Disable(); } void NeedDecreaseSparseIndexCount() noexcept { needDecreaseSparseIndexCount_ = true; } void SetOldPayloadType(PayloadType&& oldPt) noexcept { oldPayloadType_.emplace(std::move(oldPt)); } - const PayloadType& GetOldPayloadType() const noexcept { + [[nodiscard]] const PayloadType& GetOldPayloadType() const noexcept { // NOLINTNEXTLINE(bugprone-unchecked-optional-access) return *oldPayloadType_; } @@ -1128,9 +1171,8 @@ void NamespaceImpl::addIndex(const IndexDef& indexDef, bool disableTmVersionInc, newIndex->SetFields(FieldsSet{idxNo}); newIndex->UpdatePayloadType(PayloadType(payloadType_)); - FieldsSet changedFields{0, idxNo}; rollbacker.RollBacker_insertIndex(insertIndex(std::move(newIndex), idxNo, indexName)); - rollbacker.RollBacker_updateItems(updateItems(rollbacker.GetOldPayloadType(), changedFields, 1)); + rollbacker.RollBacker_updateItems(updateItems(rollbacker.GetOldPayloadType(), idxNo)); } updateSortedIdxCount(); rollbacker.Disable(); @@ -1145,17 +1187,17 @@ void NamespaceImpl::fillSparseIndex(Index& index, std::string_view jsonPath) { Payload{payloadType_, items_[rowId]}.GetByJsonPath(jsonPath, tagsMatcher_, skrefs, index.KeyType()); krefs.resize(0); bool needClearCache{false}; - index.Upsert(krefs, skrefs, rowId, needClearCache); + index.Upsert(krefs, skrefs, int(rowId), needClearCache); if (needClearCache && index.IsOrdered()) indexesCacheCleaner.Add(index.SortId()); } - markUpdated(false); + scheduleIndexOptimization(IndexOptimization::Partial); } void NamespaceImpl::doAddIndex(const IndexDef& indexDef, bool skipEqualityCheck, UpdatesContainer& pendedRepl, const NsContext& ctx) { addIndex(indexDef, ctx.inSnapshot, skipEqualityCheck); addToWAL(indexDef, WalIndexAdd, ctx); - pendedRepl.emplace_back(UpdateRecord::Type::IndexAdd, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), + pendedRepl.emplace_back(updates::URType::IndexAdd, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), indexDef); } @@ -1187,7 +1229,7 @@ bool NamespaceImpl::updateIndex(const IndexDef& indexDef, bool disableTmVersionI bool NamespaceImpl::doUpdateIndex(const IndexDef& indexDef, UpdatesContainer& pendedRepl, const NsContext& ctx) { if (updateIndex(indexDef, ctx.inSnapshot) || !ctx.GetOriginLSN().isEmpty()) { addToWAL(indexDef, WalIndexUpdate, ctx.rdxContext); - pendedRepl.emplace_back(UpdateRecord::Type::IndexUpdate, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), + pendedRepl.emplace_back(updates::URType::IndexUpdate, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), indexDef); return true; } @@ -1398,7 +1440,7 @@ void NamespaceImpl::doDelete(IdType id) { VariantArray tupleHolder; pl.Get(0, tupleHolder); - // Deleteing fields from dense and sparse indexes: + // Deleting fields from dense and sparse indexes: // we start with 1st index (not index 0) because // changing cjson of sparse index changes entire // payload value (and not only 0 item). @@ -1432,7 +1474,7 @@ void NamespaceImpl::doDelete(IdType id) { free_.resize(0); items_.resize(0); } - markUpdated(true); + markUpdated(IndexOptimization::Full); } void NamespaceImpl::removeIndex(std::unique_ptr& idx) { @@ -1466,10 +1508,10 @@ void NamespaceImpl::doTruncate(UpdatesContainer& pendedRepl, const NsContext& ct } WrSerializer ser; - WALRecord wrec(WalUpdateQuery, (ser << "TRUNCATE " << name_).Slice()); + WALRecord wrec(WalUpdateQuery, (ser << "TRUNCATE " << std::string_view(name_)).Slice()); const auto lsn = wal_.Add(wrec, ctx.GetOriginLSN()); - markUpdated(true); + markUpdated(IndexOptimization::Full); #ifdef REINDEX_WITH_V3_FOLLOWERS if (!repl_.temporary && !ctx.inSnapshot) { @@ -1477,7 +1519,7 @@ void NamespaceImpl::doTruncate(UpdatesContainer& pendedRepl, const NsContext& ct } #endif // REINDEX_WITH_V3_FOLLOWERS - pendedRepl.emplace_back(UpdateRecord::Type::Truncate, name_, lsn, repl_.nsVersion, ctx.rdxContext.EmmiterServerId()); + pendedRepl.emplace_back(updates::URType::Truncate, name_, lsn, repl_.nsVersion, ctx.rdxContext.EmmiterServerId()); } void NamespaceImpl::ModifyItem(Item& item, ItemModifyMode mode, const RdxContext& ctx) { @@ -1536,7 +1578,7 @@ ReplicationStateV2 NamespaceImpl::GetReplStateV2(const RdxContext& ctx) const { auto rlck = rLock(ctx); state.lastLsn = wal_.LastLSN(); state.dataHash = repl_.dataHash; - state.dataCount = ItemsCount(); + state.dataCount = itemsCount(); state.nsVersion = repl_.nsVersion; state.clusterStatus = repl_.clusterStatus; return state; @@ -1544,7 +1586,7 @@ ReplicationStateV2 NamespaceImpl::GetReplStateV2(const RdxContext& ctx) const { ReplicationState NamespaceImpl::getReplState() const { ReplicationState ret = repl_; - ret.dataCount = ItemsCount(); + ret.dataCount = itemsCount(); ret.lastLsn = wal_.LastLSN(); return ret; } @@ -1577,7 +1619,7 @@ void NamespaceImpl::CommitTransaction(LocalTransaction& tx, LocalQueryResults& r WALRecord initWrec(WalInitTransaction, 0, true); auto lsn = wal_.Add(initWrec, tx.GetLSN()); if (!ctx.inSnapshot) { - replicateAsync({UpdateRecord::Type::BeginTx, name_, lsn, repl_.nsVersion, ctx.rdxContext.EmmiterServerId()}, ctx.rdxContext); + replicateAsync({updates::URType::BeginTx, name_, lsn, repl_.nsVersion, ctx.rdxContext.EmmiterServerId()}, ctx.rdxContext); #ifdef REINDEX_WITH_V3_FOLLOWERS if (!repl_.temporary) { observers_.OnWALUpdate(LSNPair(lsn, lsn), name_, initWrec); @@ -1642,12 +1684,12 @@ void NamespaceImpl::CommitTransaction(LocalTransaction& tx, LocalQueryResults& r if (!ctx.inSnapshot && !ctx.isCopiedNsRequest) { // If commit happens in ns copy, than the copier have to handle replication UpdatesContainer pendedRepl; - pendedRepl.emplace_back(UpdateRecord::Type::CommitTx, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId()); + pendedRepl.emplace_back(updates::URType::CommitTx, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId()); replicate(std::move(pendedRepl), std::move(wlck), true, queryStatCalculator, ctx); return; } else if (ctx.inSnapshot && ctx.isRequireResync) { replicateAsync( - {ctx.isInitialLeaderSync ? UpdateRecord::Type::ResyncNamespaceLeaderInit : UpdateRecord::Type::ResyncNamespaceGeneric, name_, + {ctx.isInitialLeaderSync ? updates::URType::ResyncNamespaceLeaderInit : updates::URType::ResyncNamespaceGeneric, name_, lsn_t(0, 0), lsn_t(0, 0), ctx.rdxContext.EmmiterServerId()}, ctx.rdxContext); } @@ -1722,7 +1764,9 @@ void NamespaceImpl::doUpsert(ItemImpl* ritem, IdType id, bool doUpdate) { assertrx(index.Fields().getTagsPathsLength() > 0); try { plNew.GetByJsonPath(index.Fields().getTagsPath(0), skrefs, index.KeyType()); - } catch (const Error&) { + } catch (const Error& e) { + logPrintf(LogInfo, "[%s]:%d Unable to index sparse value (index name: '%s'): '%s'", name_, wal_.GetServer(), index.Name(), + e.what()); skrefs.resize(0); } } else { @@ -1738,7 +1782,9 @@ void NamespaceImpl::doUpsert(ItemImpl* ritem, IdType id, bool doUpdate) { if (isIndexSparse) { try { pl.GetByJsonPath(index.Fields().getTagsPath(0), krefs, index.KeyType()); - } catch (const Error&) { + } catch (const Error& e) { + logPrintf(LogInfo, "[%s]:%d Unable to remove sparse value from the index (index name: '%s'): '%s'", name_, + wal_.GetServer(), index.Name(), e.what()); krefs.resize(0); } } else if (index.Opts().IsArray()) { @@ -1841,7 +1887,7 @@ void NamespaceImpl::deleteItem(Item& item, UpdatesContainer& pendedRepl, const N lsn_t itemLsn(item.GetLSN()); processWalRecord(std::move(wrec), ctx, itemLsn, &item); - pendedRepl.emplace_back(ctx.inTransaction ? UpdateRecord::Type::ItemDeleteTx : UpdateRecord::Type::ItemDelete, name_, + pendedRepl.emplace_back(ctx.inTransaction ? updates::URType::ItemDeleteTx : updates::URType::ItemDelete, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), std::move(cjson)); } } @@ -1896,7 +1942,7 @@ void NamespaceImpl::doModifyItem(Item& item, ItemModifyMode mode, UpdatesContain storage_.Write(pk.Slice(), data.Slice()); } - markUpdated(!exists); + markUpdated(exists ? IndexOptimization::Partial : IndexOptimization::Full); #ifdef REINDEX_WITH_V3_FOLLOWERS if (!repl_.temporary && !ctx.inSnapshot) { @@ -1907,19 +1953,19 @@ void NamespaceImpl::doModifyItem(Item& item, ItemModifyMode mode, UpdatesContain switch (mode) { case ModeUpdate: - pendedRepl.emplace_back(ctx.inTransaction ? UpdateRecord::Type::ItemUpdateTx : UpdateRecord::Type::ItemUpdate, name_, lsn, + pendedRepl.emplace_back(ctx.inTransaction ? updates::URType::ItemUpdateTx : updates::URType::ItemUpdate, name_, lsn, repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), std::move(cjson)); break; case ModeInsert: - pendedRepl.emplace_back(ctx.inTransaction ? UpdateRecord::Type::ItemInsertTx : UpdateRecord::Type::ItemInsert, name_, lsn, + pendedRepl.emplace_back(ctx.inTransaction ? updates::URType::ItemInsertTx : updates::URType::ItemInsert, name_, lsn, repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), std::move(cjson)); break; case ModeUpsert: - pendedRepl.emplace_back(ctx.inTransaction ? UpdateRecord::Type::ItemUpsertTx : UpdateRecord::Type::ItemUpsert, name_, lsn, + pendedRepl.emplace_back(ctx.inTransaction ? updates::URType::ItemUpsertTx : updates::URType::ItemUpsert, name_, lsn, repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), std::move(cjson)); break; case ModeDelete: - pendedRepl.emplace_back(ctx.inTransaction ? UpdateRecord::Type::ItemDeleteTx : UpdateRecord::Type::ItemDelete, name_, lsn, + pendedRepl.emplace_back(ctx.inTransaction ? updates::URType::ItemDeleteTx : updates::URType::ItemDelete, name_, lsn, repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), std::move(cjson)); break; } @@ -2064,21 +2110,16 @@ void NamespaceImpl::optimizeIndexes(const NsContext& ctx) { if (cancelCommitCnt_.load(std::memory_order_relaxed)) { logPrintf(LogTrace, "Namespace::optimizeIndexes(%s) done", name_); } else { - logPrintf(LogTrace, "Namespace::optimizeIndexes(%s) was cancelled by concurent update", name_); + logPrintf(LogTrace, "Namespace::optimizeIndexes(%s) was cancelled by concurrent update", name_); } } -void NamespaceImpl::markUpdated(bool forceOptimizeAllIndexes) { +void NamespaceImpl::markUpdated(IndexOptimization requestedOptimization) { using namespace std::string_view_literals; using namespace std::chrono; itemsCount_.store(items_.size(), std::memory_order_relaxed); itemsCapacity_.store(items_.capacity(), std::memory_order_relaxed); - if (forceOptimizeAllIndexes) { - optimizationState_.store(NotOptimized); - } else { - int expected{OptimizationCompleted}; - optimizationState_.compare_exchange_strong(expected, OptimizedPartially); - } + scheduleIndexOptimization(requestedOptimization); clearNamespaceCaches(); lastUpdateTime_.store(duration_cast(system_clock_w::now().time_since_epoch()).count(), std::memory_order_release); if (!nsIsLoading_) { @@ -2086,6 +2127,19 @@ void NamespaceImpl::markUpdated(bool forceOptimizeAllIndexes) { } } +void NamespaceImpl::scheduleIndexOptimization(IndexOptimization requestedOptimization) { + switch (requestedOptimization) { + case IndexOptimization::Full: + optimizationState_.store(NotOptimized); + break; + case IndexOptimization::Partial: { + int expected{OptimizationCompleted}; + optimizationState_.compare_exchange_strong(expected, OptimizedPartially); + break; + } + } +} + Item NamespaceImpl::newItem() { auto impl_ = pool_.get(0, payloadType_, tagsMatcher_, pkFields(), schema_); impl_->tagsMatcher() = tagsMatcher_; @@ -2187,7 +2241,7 @@ void NamespaceImpl::replicateItem(IdType itemId, const NsContext& ctx, bool stat if (!statementReplication) { replicateTmUpdateIfRequired(pendedRepl, oldTmVersion, ctx); - auto sendWalUpdate = [this, itemId, &ctx, &pv, &pendedRepl](UpdateRecord::Type mode) { + auto sendWalUpdate = [this, itemId, &ctx, &pv, &pendedRepl](updates::URType mode) { lsn_t lsn; if (ctx.IsForceSyncItem()) { lsn = ctx.GetOriginLSN(); @@ -2216,11 +2270,11 @@ void NamespaceImpl::replicateItem(IdType itemId, const NsContext& ctx, bool stat itemSave.GetCJSON(cjson, false); processWalRecord(WALRecord(WalItemModify, cjson.Slice(), tagsMatcher_.version(), ModeDelete, ctx.inTransaction), ctx.rdxContext, modifyData->lsn); - pendedRepl.emplace_back(ctx.inTransaction ? UpdateRecord::Type::ItemDeleteTx : UpdateRecord::Type::ItemDelete, name_, + pendedRepl.emplace_back(ctx.inTransaction ? updates::URType::ItemDeleteTx : updates::URType::ItemDelete, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), std::move(cjson)); - sendWalUpdate(ctx.inTransaction ? UpdateRecord::Type::ItemInsertTx : UpdateRecord::Type::ItemInsert); + sendWalUpdate(ctx.inTransaction ? updates::URType::ItemInsertTx : updates::URType::ItemInsert); } else { - sendWalUpdate(ctx.inTransaction ? UpdateRecord::Type::ItemUpdateTx : UpdateRecord::Type::ItemUpdate); + sendWalUpdate(ctx.inTransaction ? updates::URType::ItemUpdateTx : updates::URType::ItemUpdate); } } @@ -2270,15 +2324,17 @@ void NamespaceImpl::doDelete(const Query& q, LocalQueryResults& result, UpdatesC WrSerializer ser; const_cast(q).type_ = QueryDelete; processWalRecord(WALRecord(WalUpdateQuery, q.GetSQL(ser, QueryDelete).Slice(), ctx.inTransaction), ctx); - pendedRepl.emplace_back(ctx.inTransaction ? UpdateRecord::Type::DeleteQueryTx : UpdateRecord::Type::DeleteQuery, name_, + pendedRepl.emplace_back(ctx.inTransaction ? updates::URType::DeleteQueryTx : updates::URType::DeleteQuery, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), std::string(ser.Slice())); } else { replicateTmUpdateIfRequired(pendedRepl, oldTmV, ctx); for (auto it : result) { WrSerializer cjson; - it.GetCJSON(cjson, false); + auto err = it.GetCJSON(cjson, false); + assertf(err.ok(), "Unabled to get CJSON after Delete-query: '%s'", err.what()); + (void)err; // There are no good ways to hadle this error processWalRecord(WALRecord(WalItemModify, cjson.Slice(), tagsMatcher_.version(), ModeDelete, ctx.inTransaction), ctx, lsn_t()); - pendedRepl.emplace_back(ctx.inTransaction ? UpdateRecord::Type::ItemDeleteTx : UpdateRecord::Type::ItemDelete, name_, + pendedRepl.emplace_back(ctx.inTransaction ? updates::URType::ItemDeleteTx : updates::URType::ItemDelete, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), std::move(cjson)); } } @@ -2332,7 +2388,7 @@ void NamespaceImpl::replicateTmUpdateIfRequired(UpdatesContainer& pendedRepl, in observers_.OnWALUpdate(LSNPair(lsn, lsn), name_, tmRec); } #endif // REINDEX_WITH_V3_FOLLOWERS - pendedRepl.emplace_back(ctx.inTransaction ? UpdateRecord::Type::SetTagsMatcherTx : UpdateRecord::Type::SetTagsMatcher, name_, lsn, + pendedRepl.emplace_back(ctx.inTransaction ? updates::URType::SetTagsMatcherTx : updates::URType::SetTagsMatcher, name_, lsn, repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), tagsMatcher_); } } @@ -2376,8 +2432,7 @@ IndexDef NamespaceImpl::getIndexDefinition(size_t i) const { } NamespaceDef NamespaceImpl::getDefinition() const { - auto pt = this->payloadType_; - NamespaceDef nsDef(name_, StorageOpts().Enabled(storage_.GetStatusCached().isEnabled)); + NamespaceDef nsDef(std::string(name_.OriginalName()), StorageOpts().Enabled(storage_.GetStatusCached().isEnabled)); nsDef.indexes.reserve(indexes_.size()); for (size_t i = 1; i < indexes_.size(); ++i) { nsDef.AddIndex(getIndexDefinition(i)); @@ -2404,7 +2459,7 @@ NamespaceMemStat NamespaceImpl::GetMemStat(const RdxContext& ctx) { ret.joinCache = joinCache_->GetMemStat(); ret.queryCache = queryCountCache_->GetMemStat(); - ret.itemsCount = ItemsCount(); + ret.itemsCount = itemsCount(); *(static_cast(&ret.replication)) = getReplState(); ret.replication.walCount = size_t(wal_.size()); ret.replication.walSize = wal_.heap_size(); @@ -2466,9 +2521,10 @@ NamespaceMemStat NamespaceImpl::GetMemStat(const RdxContext& ctx) { } NamespacePerfStat NamespaceImpl::GetPerfStat(const RdxContext& ctx) { + NamespacePerfStat ret; + auto rlck = rLock(ctx); - NamespacePerfStat ret; ret.name = name_; ret.selects = selectPerfCounter_.Get(); ret.updates = updatePerfCounter_.Get(); @@ -2566,8 +2622,10 @@ bool NamespaceImpl::loadIndexesFromStorage() { if (!status.ok()) { throw status; } - logPrintf(LogTrace, "Loaded schema(version: %lld) of namespace %s", - sysRecordsVersions_.schemaVersion ? sysRecordsVersions_.schemaVersion - 1 : 0, name_); + std::string_view schemaStr = schema_->GetJSON(); + schemaStr = std::string_view(schemaStr.data(), std::min(schemaStr.size(), kMaxSchemaCharsToPrint)); + logPrintf(LogInfo, "Loaded schema(version: %lld) of the namespace '%s'. First %d symbols of the schema are: '%s'", + sysRecordsVersions_.schemaVersion ? sysRecordsVersions_.schemaVersion - 1 : 0, name_, schemaStr.size(), schemaStr); } def.clear(); @@ -2589,7 +2647,7 @@ bool NamespaceImpl::loadIndexesFromStorage() { return false; } - int count = ser.GetVarUint(); + int count = int(ser.GetVarUint()); while (count--) { IndexDef indexDef; std::string_view indexData = ser.GetVString(); @@ -2608,7 +2666,12 @@ bool NamespaceImpl::loadIndexesFromStorage() { } } - if (schema_) schema_->BuildProtobufSchema(tagsMatcher_, payloadType_); + if (schema_) { + auto err = schema_->BuildProtobufSchema(tagsMatcher_, payloadType_); + if (!err.ok()) { + logPrintf(LogInfo, "Unable to build protobuf schema for the '%s' namespace: %s", name_, err.what()); + } + } logPrintf(LogTrace, "Loaded index structure(version %lld) of namespace '%s'\n%s", sysRecordsVersions_.idxVersion ? sysRecordsVersions_.idxVersion - 1 : 0, name_, payloadType_->ToString()); @@ -2733,7 +2796,7 @@ void NamespaceImpl::saveTagsMatcherToStorage(bool clearUpdate) { } void NamespaceImpl::EnableStorage(const std::string& path, StorageOpts opts, StorageType storageType, const RdxContext& ctx) { - std::string dbpath = fs::JoinPath(path, name_); + std::string dbpath = fs::JoinPath(path, name_.OriginalName()); auto wlck = simpleWLock(ctx); FlagGuardT nsLoadingGuard(nsIsLoading_); @@ -2825,7 +2888,7 @@ void NamespaceImpl::ApplySnapshotChunk(const SnapshotChunk& ch, bool isInitialLe observers_.OnWALUpdate(LSNPair(), name_, WALRecord(WalWALSync, ser.Slice())); } #endif // REINDEX_WITH_V3_FOLLOWERS - replicateAsync({isInitialLeaderSync ? UpdateRecord::Type::ResyncNamespaceLeaderInit : UpdateRecord::Type::ResyncNamespaceGeneric, + replicateAsync({isInitialLeaderSync ? updates::URType::ResyncNamespaceLeaderInit : updates::URType::ResyncNamespaceGeneric, name_, lsn_t(0, 0), lsn_t(0, 0), ctx.EmmiterServerId()}, ctx); } @@ -2866,7 +2929,7 @@ void NamespaceImpl::LoadFromStorage(unsigned threadsCount, const RdxContext& ctx replStateUpdates_.fetch_add(1, std::memory_order_release); } - markUpdated(true); + markUpdated(IndexOptimization::Full); } void NamespaceImpl::initWAL(int64_t minLSN, int64_t maxLSN) { @@ -2898,12 +2961,12 @@ void NamespaceImpl::removeExpiredItems(RdxActivityContext* ctx) { lastExpirationCheckTs_ = now; for (const std::unique_ptr& index : indexes_) { if ((index->Type() != IndexTtl) || (index->Size() == 0)) continue; - const int64_t expirationthreshold = + const int64_t expirationThreshold = std::chrono::duration_cast(system_clock_w::now_coarse().time_since_epoch()).count() - index->GetTTLValue(); LocalQueryResults qr; qr.AddNamespace(this, true); - doDelete(Query(name_).Where(index->Name(), CondLt, expirationthreshold), qr, pendedRepl, NsContext(rdxCtx)); + doDelete(Query(name_).Where(index->Name(), CondLt, expirationThreshold), qr, pendedRepl, NsContext(rdxCtx)); } replicate(std::move(pendedRepl), std::move(wlck), true, nullptr, RdxContext(ctx)); } @@ -2926,16 +2989,37 @@ void NamespaceImpl::removeExpiredStrings(RdxActivityContext* ctx) { } void NamespaceImpl::setSchema(std::string_view schema, UpdatesContainer& pendedRepl, const NsContext& ctx) { + using namespace std::string_view_literals; + std::string_view schemaPrint(schema.data(), std::min(schema.size(), kMaxSchemaCharsToPrint)); + std::string_view source = "user"sv; + if (ctx.inSnapshot) { + source = "snapshot"sv; + } else if (!ctx.GetOriginLSN().isEmpty()) { + source = "replication"sv; + } + logPrintf(LogInfo, "[%s]:%d Setting new schema from %s. First %d symbols are: '%s'", name_, wal_.GetServer(), source, + schemaPrint.size(), schemaPrint); schema_ = std::make_shared(schema); + const auto oldTmV = tagsMatcher_.version(); auto fields = schema_->GetPaths(); for (auto& field : fields) { tagsMatcher_.path2tag(field, true); } + if (oldTmV != tagsMatcher_.version()) { + logPrintf(LogInfo, + "[tm:%s]:%d: TagsMatcher was updated from schema. Old tm: { state_token: 0x%08X, version: %d }, new tm: { state_token: " + "0x%08X, version: %d }", + name_, wal_.GetServer(), tagsMatcher_.stateToken(), oldTmV, tagsMatcher_.stateToken(), tagsMatcher_.version()); + } - schema_->BuildProtobufSchema(tagsMatcher_, payloadType_); + auto err = schema_->BuildProtobufSchema(tagsMatcher_, payloadType_); + if (!err.ok()) { + logPrintf(LogInfo, "Unable to build protobuf schema for the '%s' namespace: %s", name_, err.what()); + } + replicateTmUpdateIfRequired(pendedRepl, oldTmV, ctx); addToWAL(schema, WalSetSchema, ctx); - pendedRepl.emplace_back(UpdateRecord::Type::SetSchema, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), + pendedRepl.emplace_back(updates::URType::SetSchema, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), std::string(schema)); } @@ -2946,6 +3030,9 @@ void NamespaceImpl::setTagsMatcher(TagsMatcher&& tm, UpdatesContainer& pendedRep if (tm.stateToken() != tagsMatcher_.stateToken()) { throw Error(errParams, "Tagsmatcher have different statetokens: %08X vs %08X", tagsMatcher_.stateToken(), tm.stateToken()); } + logPrintf(LogInfo, + "[tm:%s]:%d Set new TagsMatcher (replicated): { state_token: %08X, version: %d } -> { state_token: %08X, version: %d }", + name_, wal_.GetServer(), tagsMatcher_.stateToken(), tagsMatcher_.version(), tm.stateToken(), tm.version()); tagsMatcher_ = tm; tagsMatcher_.UpdatePayloadType(payloadType_, NeedChangeTmVersion::No); tagsMatcher_.setUpdated(); @@ -2960,7 +3047,7 @@ void NamespaceImpl::setTagsMatcher(TagsMatcher&& tm, UpdatesContainer& pendedRep observers_.OnWALUpdate(LSNPair(lsn, lsn), name_, WALRecord(WalTagsMatcher, ser.Slice(), ctx.inTransaction)); } #endif // REINDEX_WITH_V3_FOLLOWERS - pendedRepl.emplace_back(ctx.inTransaction ? UpdateRecord::Type::SetTagsMatcherTx : UpdateRecord::Type::SetTagsMatcher, name_, lsn, + pendedRepl.emplace_back(ctx.inTransaction ? updates::URType::SetTagsMatcherTx : updates::URType::SetTagsMatcher, name_, lsn, repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), std::move(tm)); saveTagsMatcherToStorage(true); @@ -3070,7 +3157,7 @@ void NamespaceImpl::putMeta(const std::string& key, std::string_view data, Updat storage_.WriteSync(StorageOpts().FillCache(), kStorageMetaPrefix + key, data); processWalRecord(WALRecord(WalPutMeta, key, data, ctx.inTransaction), ctx); - pendedRepl.emplace_back(ctx.inTransaction ? UpdateRecord::Type::PutMetaTx : UpdateRecord::Type::PutMeta, name_, wal_.LastLSN(), + pendedRepl.emplace_back(ctx.inTransaction ? updates::URType::PutMetaTx : updates::URType::PutMeta, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), key, std::string(data)); } @@ -3107,33 +3194,15 @@ void NamespaceImpl::deleteMeta(const std::string& key, UpdatesContainer& pendedR storage_.RemoveSync(StorageOpts().FillCache(), kStorageMetaPrefix + key); processWalRecord(WALRecord(WalDeleteMeta, key, ctx.inTransaction), ctx); - pendedRepl.emplace_back(UpdateRecord::Type::DeleteMeta, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), key, + pendedRepl.emplace_back(updates::URType::DeleteMeta, name_, wal_.LastLSN(), repl_.nsVersion, ctx.rdxContext.EmmiterServerId(), key, std::string()); } void NamespaceImpl::warmupFtIndexes() { - h_vector warmupThreads; - h_vector warmupIndexes; for (auto& idx : indexes_) { - if (idx->RequireWarmupOnNsCopy()) { - warmupIndexes.emplace_back(idx.get()); - } - } - auto threadsCnt = config_.optimizationSortWorkers > 0 ? std::min(unsigned(config_.optimizationSortWorkers), warmupIndexes.size()) - : std::min(4u, warmupIndexes.size()); - warmupThreads.resize(threadsCnt); - std::atomic next = {0}; - for (unsigned i = 0; i < warmupThreads.size(); ++i) { - warmupThreads[i] = std::thread([&warmupIndexes, &next] { - unsigned num = next.fetch_add(1); - while (num < warmupIndexes.size()) { - warmupIndexes[num]->CommitFulltext(); - num = next.fetch_add(1); - } - }); - } - for (auto& th : warmupThreads) { - th.join(); + if (idx->IsFulltext()) { + idx->CommitFulltext(); + } } } @@ -3148,7 +3217,7 @@ int NamespaceImpl::getSortedIdxCount() const noexcept { void NamespaceImpl::updateSortedIdxCount() { int sortedIdxCount = getSortedIdxCount(); for (auto& idx : indexes_) idx->SetSortedIdxCount(sortedIdxCount); - markUpdated(true); + scheduleIndexOptimization(IndexOptimization::Full); } IdType NamespaceImpl::createItem(size_t realSize, IdType suggestedId) { @@ -3264,7 +3333,7 @@ void NamespaceImpl::getFromJoinCacheImpl(JoinCacheRes& ctx) const { } } -void NamespaceImpl::getIndsideFromJoinCache(JoinCacheRes& ctx) const { +void NamespaceImpl::getInsideFromJoinCache(JoinCacheRes& ctx) const { if (config_.cacheMode != CacheModeAggressive || optimizationState_ != OptimizationCompleted) return; getFromJoinCacheImpl(ctx); } @@ -3310,9 +3379,9 @@ void NamespaceImpl::processWalRecord(WALRecord&& wrec, const NsContext& ctx, lsn #endif // REINDEX_WITH_V3_FOLLOWERS } -void NamespaceImpl::replicateAsync(cluster::UpdateRecord&& rec, const RdxContext& ctx) { +void NamespaceImpl::replicateAsync(updates::UpdateRecord&& rec, const RdxContext& ctx) { if (!repl_.temporary) { - auto err = clusterizator_.ReplicateAsync(std::move(rec), ctx); + auto err = observers_.SendAsyncUpdate(std::move(rec), ctx); if (!err.ok()) { throw Error(errUpdateReplication, err.what()); } @@ -3321,7 +3390,7 @@ void NamespaceImpl::replicateAsync(cluster::UpdateRecord&& rec, const RdxContext void NamespaceImpl::replicateAsync(NamespaceImpl::UpdatesContainer&& recs, const RdxContext& ctx) { if (!repl_.temporary) { - auto err = clusterizator_.ReplicateAsync(std::move(recs), ctx); + auto err = observers_.SendAsyncUpdates(std::move(recs), ctx); if (!err.ok()) { throw Error(errUpdateReplication, err.what()); } @@ -3367,4 +3436,19 @@ NamespaceImpl::IndexesCacheCleaner::~IndexesCacheCleaner() { for (auto& idx : ns_.indexes_) idx->ClearCache(sorts_); } +int64_t NamespaceImpl::correctMaxIterationsIdSetPreResult(int64_t maxIterationsIdSetPreResult) const { + auto res = maxIterationsIdSetPreResult; + static constexpr int64_t minBound = JoinedSelector::MaxIterationsForPreResultStoreValuesOptimization() + 1; + static constexpr int64_t maxBound = std::numeric_limits::max(); + if ((maxIterationsIdSetPreResult < minBound) || (maxBound < maxIterationsIdSetPreResult)) { + res = std::min(std::max(minBound, maxIterationsIdSetPreResult), maxBound); + if (!isSystem()) { + logPrintf(LogWarning, + "Namespace (%s): 'max_iterations_idset_preresult' variable is forced to be adjusted. Inputted: %d, adjusted: %d", + name_, maxIterationsIdSetPreResult, res); + } + } + return res; +} + } // namespace reindexer diff --git a/cpp_src/core/namespace/namespaceimpl.h b/cpp_src/core/namespace/namespaceimpl.h index 29b1d8661..571815cb5 100644 --- a/cpp_src/core/namespace/namespaceimpl.h +++ b/cpp_src/core/namespace/namespaceimpl.h @@ -4,10 +4,9 @@ #include #include #include -#include #include #include "asyncstorage.h" -#include "cluster/insdatareplicator.h" +#include "cluster/idatareplicator.h" #include "core/cjson/tagsmatcher.h" #include "core/dbconfig.h" #include "core/index/keyentry.h" @@ -26,6 +25,8 @@ #include "estl/fast_hash_map.h" #include "estl/shared_mutex.h" #include "estl/syncpool.h" +#include "events/observer.h" +#include "namespacename.h" #include "stringsholder.h" #include "wal/waltracker.h" @@ -43,43 +44,32 @@ template struct SelectCtxWithJoinPreSelect; struct JoinPreResult; class DBConfigProvider; -class SelectLockUpgrader; class QueryPreprocessor; class RdxContext; class RdxActivityContext; class SortExpression; class ProxiedSortExpression; -class ProtobufSchema; class LocalQueryResults; class SnapshotRecord; class Snapshot; +class SnapshotChunk; struct SnapshotOpts; -#ifdef REINDEX_WITH_V3_FOLLOWERS -class UpdatesObservers; -#endif // REINDEX_WITH_V3_FOLLOWERS - namespace long_actions { template struct Logger; - -template -struct QueryEnum2Type; } // namespace long_actions template class> class QueryStatCalculator; -template -using QueryStatCalculatorUpdDel = QueryStatCalculator, long_actions::Logger>; - namespace SortExprFuncs { struct DistanceBetweenJoinedIndexesSameNs; } // namespace SortExprFuncs class NsContext { public: - NsContext(const RdxContext &rdxCtx) : rdxContext(rdxCtx) {} + NsContext(const RdxContext &rdxCtx) noexcept : rdxContext(rdxCtx) {} NsContext &InTransaction(lsn_t stepLsn) noexcept { inTransaction = true; originLsn_ = stepLsn; @@ -102,12 +92,12 @@ class NsContext { bool IsWalSyncItem() const noexcept { return inSnapshot && isWal; } const RdxContext &rdxContext; - bool inTransaction = false; - bool inSnapshot = false; - bool isCopiedNsRequest = false; - bool isWal = false; - bool isRequireResync = false; - bool isInitialLeaderSync = false; + bool inTransaction{false}; + bool inSnapshot{false}; + bool isCopiedNsRequest{false}; + bool isWal{false}; + bool isRequireResync{false}; + bool isInitialLeaderSync{false}; private: lsn_t originLsn_; @@ -117,7 +107,17 @@ namespace composite_substitution_helpers { class CompositeSearcher; } -class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance.Padding) Padding does not matter for this class +enum class StoredValuesOptimizationStatus : int8_t { + DisabledByCompositeIndex, + DisabledByFullTextIndex, + DisabledByJoinedFieldSort, + Enabled +}; + +enum class IndexOptimization : int8_t { Partial, Full }; + +class NamespaceImpl final : public intrusive_atomic_rc_base { // NOLINT(*performance.Padding) Padding does not + // matter for this class class RollBack_insertIndex; class RollBack_addIndex; template @@ -142,7 +142,7 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. ~IndexesCacheCleaner(); private: - NamespaceImpl &ns_; + const NamespaceImpl &ns_; std::bitset sorts_; }; @@ -186,17 +186,17 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. private: const NamespaceImpl &ns_; - const int sorted_indexes_; - const IdType curSortId_; + const int sorted_indexes_{0}; + const IdType curSortId_{-1}; std::vector ids2Sorts_; - int64_t ids2SortsMemSize_ = 0; + int64_t ids2SortsMemSize_{0}; }; - class IndexesStorage : public std::vector> { + class IndexesStorage final : public std::vector> { public: using Base = std::vector>; - IndexesStorage(const NamespaceImpl &ns); + explicit IndexesStorage(const NamespaceImpl &ns) noexcept; IndexesStorage(const IndexesStorage &src) = delete; IndexesStorage &operator=(const IndexesStorage &src) = delete; @@ -204,28 +204,27 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. IndexesStorage(IndexesStorage &&src) = delete; IndexesStorage &operator=(IndexesStorage &&src) noexcept = delete; - int denseIndexesSize() const { return ns_.payloadType_.NumFields(); } - int sparseIndexesSize() const { return ns_.sparseIndexesCount_; } - int compositeIndexesSize() const { return totalSize() - denseIndexesSize() - sparseIndexesSize(); } - void MoveBase(IndexesStorage &&src); - int firstSparsePos() const { return ns_.payloadType_.NumFields(); } - int firstCompositePos() const { return ns_.payloadType_.NumFields() + ns_.sparseIndexesCount_; } - int firstCompositePos(const PayloadType &pt, int sparseIndexes) const { return pt.NumFields() + sparseIndexes; } - - int totalSize() const { return size(); } + int denseIndexesSize() const noexcept { return ns_.payloadType_.NumFields(); } + int sparseIndexesSize() const noexcept { return ns_.sparseIndexesCount_; } + int compositeIndexesSize() const noexcept { return totalSize() - denseIndexesSize() - sparseIndexesSize(); } + int firstSparsePos() const noexcept { return ns_.payloadType_.NumFields(); } + int firstCompositePos() const noexcept { return ns_.payloadType_.NumFields() + ns_.sparseIndexesCount_; } + int firstCompositePos(const PayloadType &pt, int sparseIndexes) const noexcept { return pt.NumFields() + sparseIndexes; } + int totalSize() const noexcept { return size(); } private: const NamespaceImpl &ns_; }; - class Items : public std::vector { + class Items final : public std::vector { public: - bool exists(IdType id) const { return id < IdType(size()) && !at(id).IsFree(); } + bool exists(IdType id) const noexcept { return id < IdType(size()) && !(*this)[id].IsFree(); } }; public: - using UpdatesContainer = h_vector; + using UpdatesContainer = h_vector; enum OptimizationState : int { NotOptimized, OptimizedPartially, OptimizationCompleted }; + enum class FieldChangeType { Add = 1, Delete = -1 }; enum class InvalidationType : int { Valid, Readonly, OverwrittenByUser, OverwrittenByReplicator }; using Ptr = intrusive_ptr; @@ -238,6 +237,7 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. using MutexType = Mutex; NsWLock() = default; + NsWLock(MutexType &mtx, std::try_to_lock_t t, const RdxContext &ctx, bool isCL) : impl_(mtx, t, ctx), isClusterLck_(isCL) {} NsWLock(MutexType &mtx, const RdxContext &ctx, bool isCL) : impl_(mtx, ctx), isClusterLck_(isCL) {} NsWLock(const NsWLock &) = delete; NsWLock(NsWLock &&) = default; @@ -255,7 +255,7 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. typedef contexted_shared_lock RLockT; typedef NsWLock WLockT; - Locker(cluster::INsDataReplicator &clusterizator, NamespaceImpl &owner) : clusterizator_(clusterizator), owner_(owner) {} + Locker(const cluster::IDataSyncer &syncer, const NamespaceImpl &owner) noexcept : syncer_(syncer), owner_(owner) {} RLockT RLock(const RdxContext &ctx) const { return RLockT(mtx_, ctx); } WLockT DataWLock(const RdxContext &ctx, bool skipClusterStatusCheck) const { @@ -264,16 +264,16 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. const bool requireSync = !ctx.NoWaitSync() && ctx.GetOriginLSN().isEmpty() && !owner_.isSystem(); const bool isFollowerNS = owner_.repl_.clusterStatus.role == ClusterizationStatus::Role::SimpleReplica || owner_.repl_.clusterStatus.role == ClusterizationStatus::Role::ClusterReplica; - bool synchronized = isFollowerNS || !requireSync || clusterizator_.IsInitialSyncDone(owner_.name_); + bool synchronized = isFollowerNS || !requireSync || syncer_.IsInitialSyncDone(owner_.name_); while (!synchronized) { // This is required in case of rename during sync wait auto name = owner_.name_; lck.unlock(); - clusterizator_.AwaitInitialSync(name, ctx); + syncer_.AwaitInitialSync(name, ctx); lck.lock(); checkInvalidation(); - synchronized = clusterizator_.IsInitialSyncDone(owner_.name_); + synchronized = syncer_.IsInitialSyncDone(owner_.name_); } if (!skipClusterStatusCheck) { @@ -287,11 +287,7 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. checkInvalidation(); return lck; } - std::unique_lock StorageLock() const { - std::unique_lock lck(storageMtx_); - checkInvalidation(); - return lck; - } + bool IsNotLocked(const RdxContext &ctx) const { return WLockT(mtx_, std::try_to_lock_t{}, ctx, false).owns_lock(); } void MarkReadOnly() noexcept { invalidation_.store(int(InvalidationType::Readonly), std::memory_order_release); } void MarkOverwrittenByUser() noexcept { invalidation_.store(int(InvalidationType::OverwrittenByUser), std::memory_order_release); } void MarkOverwrittenByForceSync() noexcept { @@ -301,6 +297,7 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. bool IsValid() const noexcept { return NamespaceImpl::InvalidationType(invalidation_.load(std::memory_order_acquire)) == InvalidationType::Valid; } + const cluster::IDataSyncer &Syncer() const noexcept { return syncer_; } private: void checkInvalidation() const { @@ -319,22 +316,16 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. } mutable Mutex mtx_; - mutable std::mutex storageMtx_; std::atomic invalidation_ = {int(InvalidationType::Valid)}; - cluster::INsDataReplicator &clusterizator_; - NamespaceImpl &owner_; + const cluster::IDataSyncer &syncer_; + const NamespaceImpl &owner_; }; -#ifdef REINDEX_WITH_V3_FOLLOWERS - NamespaceImpl(const std::string &_name, std::optional stateToken, cluster::INsDataReplicator &clusterizator, - UpdatesObservers &); -#else - NamespaceImpl(const std::string &_name, std::optional stateToken, cluster::INsDataReplicator &clusterizator); -#endif // REINDEX_WITH_V3_FOLLOWERS + NamespaceImpl(const std::string &_name, std::optional stateToken, const cluster::IDataSyncer &, UpdatesObservers &); NamespaceImpl &operator=(const NamespaceImpl &) = delete; - ~NamespaceImpl(); + ~NamespaceImpl() override; - std::string GetName(const RdxContext &ctx) const { + NamespaceName GetName(const RdxContext &ctx) const { auto rlck = rLock(ctx); return name_; } @@ -437,6 +428,7 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. lsn_t lsn; }; + Error rebuildIndexesTagsPaths(const TagsMatcher &newTm); ReplicationState getReplState() const; std::string sysRecordName(std::string_view sysTag, uint64_t version); void writeSysRecToStorage(std::string_view data, std::string_view sysTag, uint64_t &version, bool direct); @@ -451,7 +443,8 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. void initWAL(int64_t minLSN, int64_t maxLSN); - void markUpdated(bool forceOptimizeAllIndexes); + void markUpdated(IndexOptimization requestedOptimization); + void scheduleIndexOptimization(IndexOptimization requestedOptimization); Item newItem(); void doUpdate(const Query &query, LocalQueryResults &result, UpdatesContainer &pendedRepl, const NsContext &); void doDelete(const Query &query, LocalQueryResults &result, UpdatesContainer &pendedRepl, const NsContext &); @@ -460,9 +453,8 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. void deleteItem(Item &item, UpdatesContainer &pendedRepl, const NsContext &ctx); void doModifyItem(Item &item, ItemModifyMode mode, UpdatesContainer &pendedRepl, const NsContext &ctx, IdType suggestedId = -1); void updateTagsMatcherFromItem(ItemImpl *ritem, const NsContext &ctx); - template - [[nodiscard]] RollBack_updateItems updateItems(const PayloadType &oldPlType, const FieldsSet &changedFields, - int deltaFields); + template + [[nodiscard]] RollBack_updateItems updateItems(const PayloadType &oldPlType, int changedField); void fillSparseIndex(Index &, std::string_view jsonPath); void doDelete(IdType id); void doTruncate(UpdatesContainer &pendedRepl, const NsContext &ctx); @@ -517,7 +509,7 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. void getFromJoinCache(const Query &, const JoinedQuery &, JoinCacheRes &out) const; void getFromJoinCache(const Query &, JoinCacheRes &out) const; void getFromJoinCacheImpl(JoinCacheRes &out) const; - void getIndsideFromJoinCache(JoinCacheRes &ctx) const; + void getInsideFromJoinCache(JoinCacheRes &ctx) const; int64_t lastUpdateTimeNano() const noexcept { return repl_.updatedUnixNano; } const FieldsSet &pkFields(); @@ -529,6 +521,7 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. using namespace std::chrono; lastSelectTime_ = duration_cast(system_clock_w::now().time_since_epoch()).count(); } + bool hadSelects() const noexcept { return lastSelectTime_.load(std::memory_order_relaxed) != 0; } void markReadOnly() noexcept { locker_.MarkReadOnly(); } void markOverwrittenByUser() noexcept { locker_.MarkOverwrittenByUser(); } void markOverwrittenByForceSync() noexcept { locker_.MarkOverwrittenByForceSync(); } @@ -536,6 +529,7 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. Locker::WLockT dataWLock(const RdxContext &ctx, bool skipClusterStatusCheck = false) const { return locker_.DataWLock(ctx, skipClusterStatusCheck); } + bool isNotLocked(const RdxContext &ctx) const { return locker_.IsNotLocked(ctx); } Locker::RLockT rLock(const RdxContext &ctx) const { return locker_.RLock(ctx); } void checkClusterRole(const RdxContext &ctx) const { checkClusterRole(ctx.GetOriginLSN()); } void checkClusterRole(lsn_t originLsn) const; @@ -545,14 +539,15 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. bool SortOrdersBuilt() const noexcept { return optimizationState_.load(std::memory_order_acquire) == OptimizationCompleted; } + int64_t correctMaxIterationsIdSetPreResult(int64_t maxIterationsIdSetPreResult) const; + IndexesStorage indexes_; fast_hash_map indexesNames_; fast_hash_map> indexesToComposites_; // Maps index fields to corresponding composite indexes // All items with data Items items_; std::vector free_; - // NamespaceImpl name - std::string name_; + NamespaceName name_; // Payload types PayloadType payloadType_; @@ -560,11 +555,11 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. TagsMatcher tagsMatcher_; AsyncStorage storage_; - std::atomic replStateUpdates_ = {0}; + std::atomic replStateUpdates_{0}; std::unordered_map meta_; - int sparseIndexesCount_ = 0; + int sparseIndexesCount_{0}; VariantArray krefs, skrefs; SysRecordsVersions sysRecordsVersions_; @@ -574,8 +569,8 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. StringsHolderPtr strHolder() const noexcept { return strHolder_; } std::set GetFTIndexes(const RdxContext &) const; - size_t ItemsCount() const noexcept { return items_.size() - free_.size(); } - const NamespaceConfigData &Config() const noexcept { return config_; } + size_t itemsCount() const noexcept { return items_.size() - free_.size(); } + const NamespaceConfigData &config() const noexcept { return config_; } void DumpIndex(std::ostream &os, std::string_view index, const RdxContext &ctx) const; @@ -585,14 +580,14 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. IdType createItem(size_t realSize, IdType suggestedId); void processWalRecord(WALRecord &&wrec, const NsContext &ctx, lsn_t itemLsn = lsn_t(), Item *item = nullptr); - void replicateAsync(cluster::UpdateRecord &&rec, const RdxContext &ctx); + void replicateAsync(updates::UpdateRecord &&rec, const RdxContext &ctx); void replicateAsync(UpdatesContainer &&recs, const RdxContext &ctx); template void replicate(UpdatesContainer &&recs, NamespaceImpl::Locker::WLockT &&wlck, bool tryForceFlush, QueryStatsCalculatorT &&statCalculator, const NsContext &ctx) { if (!repl_.temporary) { assertrx(!ctx.isCopiedNsRequest); - auto err = clusterizator_.Replicate( + auto err = observers_.SendUpdates( std::move(recs), [&wlck]() { assertrx(wlck.isClusterLck()); @@ -629,9 +624,10 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. } size_t getWalSize(const NamespaceConfigData &cfg) const noexcept { return isSystem() ? int64_t(1) : std::max(cfg.walSize, int64_t(1)); } void clearNamespaceCaches(); + std::vector pickJsonPath(const PayloadFieldType &fld); PerfStatCounterMT updatePerfCounter_, selectPerfCounter_; - std::atomic enablePerfCounters_; + std::atomic_bool enablePerfCounters_{false}; NamespaceConfigData config_; std::unique_ptr queryCountCache_; @@ -641,29 +637,25 @@ class NamespaceImpl : public intrusive_atomic_rc_base { // NOLINT(*performance. ReplicationState repl_; StorageOpts storageOpts_; - std::atomic lastSelectTime_; + std::atomic_int64_t lastSelectTime_{0}; sync_pool pool_; - std::atomic cancelCommitCnt_{0}; - std::atomic lastUpdateTime_; + std::atomic_int32_t cancelCommitCnt_{0}; + std::atomic_int64_t lastUpdateTime_{0}; - std::atomic itemsCount_ = {0}; - std::atomic itemsCapacity_ = {0}; - bool nsIsLoading_; + std::atomic_uint32_t itemsCount_{0}; + std::atomic_uint32_t itemsCapacity_{0}; + bool nsIsLoading_{false}; - size_t itemsDataSize_ = 0; + size_t itemsDataSize_{0}; - std::atomic optimizationState_{OptimizationState::NotOptimized}; + std::atomic_int optimizationState_{OptimizationState::NotOptimized}; StringsHolderPtr strHolder_; std::deque strHoldersWaitingToBeDeleted_; std::chrono::seconds lastExpirationCheckTs_{0}; - mutable std::atomic nsUpdateSortedContextMemory_ = {0}; - cluster::INsDataReplicator &clusterizator_; + mutable std::atomic nsUpdateSortedContextMemory_{0}; std::atomic dbDestroyed_{false}; lsn_t incarnationTag_; // Determines unique namespace incarnation for the correct go cache invalidation - -#ifdef REINDEX_WITH_V3_FOLLOWERS UpdatesObservers &observers_; -#endif // REINDEX_WITH_V3_FOLLOWERS }; } // namespace reindexer diff --git a/cpp_src/core/namespace/namespacename.h b/cpp_src/core/namespace/namespacename.h new file mode 100644 index 000000000..ba0b3de1e --- /dev/null +++ b/cpp_src/core/namespace/namespacename.h @@ -0,0 +1,96 @@ +#pragma once + +#include "estl/h_vector.h" +#include "estl/intrusive_ptr.h" +#include "tools/stringstools.h" + +namespace reindexer { + +namespace namespace_name_impl { +class NamespaceNameImpl { +public: + using HasherT = nocase_hash_str; + + explicit NamespaceNameImpl(std::string_view name) { + size_t i = 0; + data_.resize(name.size()); + for (auto it = data_.begin(), itEnd = data_.end(); it != itEnd; ++it, ++i) { + (*it) = tolower(name[i]); + } + hash_ = HasherT()(std::string_view(data_.data(), data_.size())); + if (std::memcmp(name.data(), data_.data(), name.size()) != 0) { + originalNameStart_ = data_.size(); + data_.insert(data_.end(), name.begin(), name.end()); + } else { + originalNameStart_ = 0; + } + } + + size_t Hash() const noexcept { return hash_; } + bool Empty() const noexcept { return data_.empty(); } + size_t Size() const noexcept { return data_.size(); } + std::string_view OriginalName() const noexcept { + return std::string_view(data_.data() + originalNameStart_, data_.size() - originalNameStart_); + } + operator std::string_view() const noexcept { return std::string_view(data_.data(), data_.size() - originalNameStart_); } + +private: + using VecT = h_vector; + + size_t hash_; + VecT::size_type originalNameStart_; + h_vector data_; +}; +} // namespace namespace_name_impl + +class NamespaceName { + using ValueT = intrusive_atomic_rc_wrapper; + +public: + NamespaceName() = default; + explicit NamespaceName(std::string_view name) : impl_(make_intrusive(name)) {} + + size_t hash() const noexcept { return impl_ ? impl_->Hash() : 0; } + bool empty() const noexcept { return !impl_ || impl_->Empty(); } + size_t size() const noexcept { return impl_ ? impl_->Size() : 0; } + + std::string_view OriginalName() const noexcept { return impl_ ? impl_->OriginalName() : std::string_view(); } + operator std::string_view() const noexcept { return impl_ ? std::string_view(*impl_) : std::string_view(); } + +private: + intrusive_ptr impl_; +}; + +inline std::ostream& operator<<(std::ostream& os, const NamespaceName& name) { return os << name.OriginalName(); } +inline bool operator==(const NamespaceName& lhs, const NamespaceName& rhs) noexcept { + return std::string_view(lhs) == std::string_view(rhs); +} + +struct NamespaceNameEqual { + using is_transparent = void; + + bool operator()(const NamespaceName& lhs, const NamespaceName& rhs) const noexcept { return lhs == rhs; } + bool operator()(std::string_view lhs, std::string_view rhs) const noexcept { return iequals(lhs, rhs); } + bool operator()(const NamespaceName& lhs, std::string_view rhs) const noexcept { return iequals(lhs, rhs); } + bool operator()(std::string_view lhs, const NamespaceName& rhs) const noexcept { return iequals(lhs, rhs); } +}; + +struct NamespaceNameLess { + using is_transparent = void; + + bool operator()(const NamespaceName& lhs, const NamespaceName& rhs) const noexcept { + return std::string_view(lhs) < std::string_view(rhs); + } + bool operator()(std::string_view lhs, std::string_view rhs) const noexcept { return iless(lhs, rhs); } + bool operator()(const NamespaceName& lhs, std::string_view rhs) const noexcept { return iless(lhs, rhs); } + bool operator()(std::string_view lhs, const NamespaceName& rhs) const noexcept { return iless(lhs, rhs); } +}; + +struct NamespaceNameHash { + using is_transparent = void; + + size_t operator()(std::string_view hs) const noexcept { return namespace_name_impl::NamespaceNameImpl::HasherT()(hs); } + size_t operator()(const NamespaceName& hs) const noexcept { return hs.hash(); } +}; + +} // namespace reindexer diff --git a/cpp_src/core/namespace/namespacenamesets.h b/cpp_src/core/namespace/namespacenamesets.h new file mode 100644 index 000000000..f08e1e870 --- /dev/null +++ b/cpp_src/core/namespace/namespacenamesets.h @@ -0,0 +1,10 @@ +#pragma once + +#include "estl/fast_hash_set.h" +#include "namespacename.h" + +namespace reindexer { + +using NsNamesHashSetT = fast_hash_set; + +} diff --git a/cpp_src/core/namespace/namespacestat.cc b/cpp_src/core/namespace/namespacestat.cc index 95946c919..710c0b18f 100644 --- a/cpp_src/core/namespace/namespacestat.cc +++ b/cpp_src/core/namespace/namespacestat.cc @@ -2,8 +2,6 @@ #include "core/cjson/jsonbuilder.h" #include "gason/gason.h" -#include "tools/jsontools.h" -#include "tools/logger.h" namespace reindexer { @@ -12,7 +10,7 @@ using namespace std::string_view_literals; void NamespaceMemStat::GetJSON(WrSerializer &ser) { JsonBuilder builder(ser); - builder.Put("name", name); + builder.Put("name", name.OriginalName()); builder.Put("items_count", itemsCount); if (emptyItemsCount) builder.Put("empty_items_count", emptyItemsCount); @@ -96,7 +94,7 @@ void PerfStat::GetJSON(JsonBuilder &builder) { void NamespacePerfStat::GetJSON(WrSerializer &ser) { JsonBuilder builder(ser); - builder.Put("name", name); + builder.Put("name", name.OriginalName()); { auto obj = builder.Object("updates"); updates.GetJSON(obj); diff --git a/cpp_src/core/namespace/namespacestat.h b/cpp_src/core/namespace/namespacestat.h index 9919eef78..6a08d10d7 100644 --- a/cpp_src/core/namespace/namespacestat.h +++ b/cpp_src/core/namespace/namespacestat.h @@ -1,12 +1,11 @@ #pragma once #include -#include -#include #include #include #include "estl/span.h" #include "gason/gason.h" +#include "namespacename.h" #include "tools/errors.h" #include "tools/lsn.h" @@ -126,7 +125,7 @@ struct ReplicationStat : public ReplicationState { struct NamespaceMemStat { void GetJSON(WrSerializer &ser); - std::string name; + NamespaceName name; std::string storagePath; bool storageOK = false; bool storageEnabled = false; @@ -195,7 +194,7 @@ struct IndexPerfStat { struct NamespacePerfStat { void GetJSON(WrSerializer &ser); - std::string name; + NamespaceName name; PerfStat updates; PerfStat selects; TxPerfStat transactions; diff --git a/cpp_src/core/namespace/snapshot/snapshothandler.cc b/cpp_src/core/namespace/snapshot/snapshothandler.cc index c41a6cc57..801f1490c 100644 --- a/cpp_src/core/namespace/snapshot/snapshothandler.cc +++ b/cpp_src/core/namespace/snapshot/snapshothandler.cc @@ -21,7 +21,7 @@ Snapshot SnapshotHandler::CreateSnapshot(const SnapshotOpts& opts) const { selCtx.contextCollectingMode = true; WALSelecter selecter(&ns_, false); selecter(walQr, selCtx, true); - return Snapshot(ns_.payloadType_, ns_.tagsMatcher_, ns_.repl_.nsVersion, ns_.wal_.LastLSN(), ns_.repl_.dataHash, ns_.ItemsCount(), + return Snapshot(ns_.payloadType_, ns_.tagsMatcher_, ns_.repl_.nsVersion, ns_.wal_.LastLSN(), ns_.repl_.dataHash, ns_.itemsCount(), ns_.repl_.clusterStatus, std::move(walQr)); } catch (Error& err) { if (err.code() != errOutdatedWAL) { @@ -29,7 +29,7 @@ Snapshot SnapshotHandler::CreateSnapshot(const SnapshotOpts& opts) const { } const auto minLsn = ns_.wal_.LSNByOffset(opts.maxWalDepthOnForceSync); if (minLsn.isEmpty()) { - return Snapshot(ns_.tagsMatcher_, ns_.repl_.nsVersion, ns_.repl_.dataHash, ns_.ItemsCount(), ns_.repl_.clusterStatus); + return Snapshot(ns_.tagsMatcher_, ns_.repl_.nsVersion, ns_.repl_.dataHash, ns_.itemsCount(), ns_.repl_.clusterStatus); } { Query q = Query(ns_.name_).Where("#lsn", CondGe, int64_t(minLsn)); @@ -52,12 +52,12 @@ Snapshot SnapshotHandler::CreateSnapshot(const SnapshotOpts& opts) const { selecter(fullQr, selCtx, true); } - return Snapshot(ns_.payloadType_, ns_.tagsMatcher_, ns_.repl_.nsVersion, ns_.wal_.LastLSN(), ns_.repl_.dataHash, ns_.ItemsCount(), + return Snapshot(ns_.payloadType_, ns_.tagsMatcher_, ns_.repl_.nsVersion, ns_.wal_.LastLSN(), ns_.repl_.dataHash, ns_.itemsCount(), ns_.repl_.clusterStatus, std::move(walQr), std::move(fullQr)); } } -void SnapshotHandler::ApplyChunk(const SnapshotChunk& ch, bool isInitialLeaderSync, h_vector& repl) { +void SnapshotHandler::ApplyChunk(const SnapshotChunk& ch, bool isInitialLeaderSync, h_vector& repl) { ChunkContext ctx; ctx.wal = ch.IsWAL(); ctx.shallow = ch.IsShallow(); @@ -67,7 +67,7 @@ void SnapshotHandler::ApplyChunk(const SnapshotChunk& ch, bool isInitialLeaderSy } } -void SnapshotHandler::applyRecord(const SnapshotRecord& snRec, const ChunkContext& ctx, h_vector& pendedRepl) { +void SnapshotHandler::applyRecord(const SnapshotRecord& snRec, const ChunkContext& ctx, h_vector& pendedRepl) { Error err; if (ctx.shallow) { auto unpacked = snRec.Unpack(); @@ -119,7 +119,7 @@ Error SnapshotHandler::applyShallowRecord(lsn_t lsn, WALRecType type, const Pack } Error SnapshotHandler::applyRealRecord(lsn_t lsn, const SnapshotRecord& snRec, const ChunkContext& chCtx, - h_vector& pendedRepl) { + h_vector& pendedRepl) { Error err; IndexDef iDef; Item item; diff --git a/cpp_src/core/namespace/snapshot/snapshothandler.h b/cpp_src/core/namespace/snapshot/snapshothandler.h index 0181c0f1e..2f8637d48 100644 --- a/cpp_src/core/namespace/snapshot/snapshothandler.h +++ b/cpp_src/core/namespace/snapshot/snapshothandler.h @@ -1,6 +1,6 @@ #pragma once -#include "cluster/updaterecord.h" +#include "updates/updaterecord.h" #include "snapshot.h" namespace reindexer { @@ -13,7 +13,7 @@ class SnapshotHandler { SnapshotHandler(NamespaceImpl& ns) : ns_(ns) {} Snapshot CreateSnapshot(const SnapshotOpts& opts) const; - void ApplyChunk(const SnapshotChunk& ch, bool isInitialLeaderSync, h_vector& repl); + void ApplyChunk(const SnapshotChunk& ch, bool isInitialLeaderSync, h_vector& repl); private: struct ChunkContext { @@ -23,9 +23,9 @@ class SnapshotHandler { bool initialLeaderSync = false; }; - void applyRecord(const SnapshotRecord& rec, const ChunkContext& ctx, h_vector& repl); + void applyRecord(const SnapshotRecord& rec, const ChunkContext& ctx, h_vector& repl); Error applyShallowRecord(lsn_t lsn, WALRecType type, const PackedWALRecord& wrec, const ChunkContext& chCtx); - Error applyRealRecord(lsn_t lsn, const SnapshotRecord& snRec, const ChunkContext& chCtx, h_vector& repl); + Error applyRealRecord(lsn_t lsn, const SnapshotRecord& snRec, const ChunkContext& chCtx, h_vector& repl); NamespaceImpl& ns_; RdxContext dummyCtx_; diff --git a/cpp_src/core/nsselecter/aggregator.cc b/cpp_src/core/nsselecter/aggregator.cc index f3d732087..a336862b9 100644 --- a/cpp_src/core/nsselecter/aggregator.cc +++ b/cpp_src/core/nsselecter/aggregator.cc @@ -110,6 +110,7 @@ class Aggregator::SinglefieldComparator { Aggregator::MultifieldComparator::MultifieldComparator(const h_vector &sortingEntries, const FieldsSet &fields, const PayloadType &type) : compOpts_{}, type_{type}, haveCompareByCount{false} { + assertrx_throw(type_); if (sortingEntries.empty()) { compOpts_.emplace_back(fields, Asc); return; @@ -143,11 +144,10 @@ Aggregator::MultifieldComparator::MultifieldComparator(const h_vector(rhs, opt.fields); if (less == ComparationResult::Eq) continue; return toSigned(less) * opt.direction < 0; @@ -156,14 +156,13 @@ bool Aggregator::MultifieldComparator::operator()(const PayloadValue &lhs, const } bool Aggregator::MultifieldComparator::operator()(const std::pair &lhs, const std::pair &rhs) const { + assertrx_throw(!lhs.first.IsFree()); + assertrx_throw(!rhs.first.IsFree()); for (const auto &opt : compOpts_) { if (opt.fields.empty()) { if (lhs.second == rhs.second) continue; return opt.direction * (lhs.second - rhs.second) < 0; } - assertrx(type_); - assertrx(!lhs.first.IsFree()); - assertrx(!rhs.first.IsFree()); const auto less = ConstPayload(type_, lhs.first).Compare(rhs.first, opt.fields); if (less == ComparationResult::Eq) continue; return toSigned(less) * opt.direction < 0; @@ -320,7 +319,7 @@ AggregationResult Aggregator::GetResult() const { *facets_); break; case AggDistinct: - assertrx(distincts_); + assertrx_dbg(distincts_); ret.payloadType = payloadType_; ret.distinctsFields = fields_; ret.distincts.reserve(distincts_->size()); @@ -331,7 +330,7 @@ AggregationResult Aggregator::GetResult() const { case AggCount: case AggCountCached: case AggUnknown: - abort(); + throw_as_assert; } return ret; } @@ -356,7 +355,7 @@ void Aggregator::Aggregate(const PayloadValue &data) { return; } - assertrx(fields_.size() == 1); + assertrx_dbg(fields_.size() == 1); if (fields_[0] == IndexValueType::SetByJsonPath) { ConstPayload pl(payloadType_, data); VariantArray va; @@ -396,11 +395,11 @@ void Aggregator::aggregate(const Variant &v) { break; case AggFacet: std::visit(overloaded{[&v](SinglefieldUnorderedMap &fm) { ++fm[v]; }, [&v](SinglefieldOrderedMap &fm) { ++fm[v]; }, - [](MultifieldUnorderedMap &) { assertrx(0); }, [](MultifieldOrderedMap &) { assertrx(0); }}, + [](MultifieldUnorderedMap &) { throw_as_assert; }, [](MultifieldOrderedMap &) { throw_as_assert; }}, *facets_); break; case AggDistinct: - assertrx(distincts_); + assertrx_dbg(distincts_); distincts_->insert(v); break; case AggUnknown: diff --git a/cpp_src/core/nsselecter/aggregator.h b/cpp_src/core/nsselecter/aggregator.h index aefec5e2c..24e1d6ce3 100644 --- a/cpp_src/core/nsselecter/aggregator.h +++ b/cpp_src/core/nsselecter/aggregator.h @@ -71,7 +71,7 @@ class Aggregator { [&](KeyValueType::Composite) { return ConstPayload(type_, static_cast(v1)).IsEQ(static_cast(v2), fields_); }, - [](OneOf) noexcept -> bool { abort(); }); + [](OneOf) -> bool { throw_as_assert; }); } private: @@ -85,9 +85,7 @@ class Aggregator { [&](OneOf) noexcept { return v.Hash(); }, [&](KeyValueType::Composite) { return ConstPayload(type_, static_cast(v)).GetHash(fields_); }, - [](OneOf) noexcept -> size_t { abort(); }); - assertrx(type_); - return 0; + [](OneOf) -> size_t { throw_as_assert; }); } private: @@ -98,10 +96,9 @@ class Aggregator { class DistinctChangeChecker { public: DistinctChangeChecker(const Aggregator &aggregator) noexcept : aggregator_(aggregator) {} - bool operator()() noexcept { - assertrx_throw(aggregator_.Type() == AggType::AggDistinct); + [[nodiscard]] bool operator()() noexcept { + assertrx_dbg(aggregator_.Type() == AggType::AggDistinct); assertrx_dbg(aggregator_.distincts_); - size_t prev = lastCheckSize_; lastCheckSize_ = aggregator_.distincts_->size(); return aggregator_.distincts_->size() > prev; diff --git a/cpp_src/core/nsselecter/btreeindexiterator.h b/cpp_src/core/nsselecter/btreeindexiterator.h index 7a0b6bc25..c31574eb7 100644 --- a/cpp_src/core/nsselecter/btreeindexiterator.h +++ b/cpp_src/core/nsselecter/btreeindexiterator.h @@ -27,7 +27,7 @@ class BtreeIndexIterator final : public IndexIterator { } bool Next() noexcept final override { - assertrx(impl_); + assertrx_dbg(impl_); if (impl_->isOver()) { return impl_->finishIteration(); } @@ -42,12 +42,12 @@ class BtreeIndexIterator final : public IndexIterator { } void ExcludeLastSet() noexcept override { - assertrx(impl_); + assertrx_dbg(impl_); impl_->shiftToNextIdset(); } IdType Value() const noexcept override final { - assertrx(impl_); + assertrx_dbg(impl_); return impl_->getValue(); } size_t GetMaxIterations(size_t limitIters) noexcept final { diff --git a/cpp_src/core/nsselecter/comparator/comparator_indexed.cc b/cpp_src/core/nsselecter/comparator/comparator_indexed.cc index 5e5f2314f..6a7c87f0a 100644 --- a/cpp_src/core/nsselecter/comparator/comparator_indexed.cc +++ b/cpp_src/core/nsselecter/comparator/comparator_indexed.cc @@ -83,22 +83,6 @@ typename reindexer::comparators::ValuesHolder::Type } } -template -typename reindexer::comparators::ValuesHolder::Type getInitCompositeValues( - const reindexer::VariantArray& values, const reindexer::PayloadType& payloadType, const reindexer::FieldsSet& fields) { - if constexpr (Cond == CondRange) { - return {reindexer::comparators::GetValue(Cond, values, 0), - reindexer::comparators::GetValue(Cond, values, 1)}; - } else if constexpr (Cond == CondSet) { - return reindexer::unordered_payload_set{values.size(), reindexer::hash_composite{payloadType, fields}, - reindexer::equal_composite{payloadType, fields}}; - } else if constexpr (Cond == CondAllSet) { - return {{reindexer::PayloadType{payloadType}, reindexer::FieldsSet{fields}}, {}}; - } else if constexpr (Cond == CondEq || Cond == CondLt || Cond == CondLe || Cond == CondGt || Cond == CondGe || Cond == CondLike) { - return reindexer::comparators::GetValue(Cond, values, 0); - } -} - template void initComparator(const reindexer::VariantArray& from, typename reindexer::comparators::ValuesHolder::Type& to) { if constexpr (Cond == CondSet) { @@ -512,8 +496,8 @@ ComparatorIndexedComposite::ComparatorIndexedComposite(const VariantArray& value break; case CondSet: value_.~PayloadValue(); - new (&setPtr_) SetPtrType{make_intrusive( - SetType{values.size(), reindexer::hash_composite{payloadType, fields}, reindexer::equal_composite{payloadType, fields}})}; + new (&setPtr_) SetPtrType{make_intrusive(SetType{values.size(), reindexer::hash_composite_ref{payloadType, fields}, + reindexer::equal_composite_ref{payloadType, fields}})}; initComparator(values, *setPtr_); break; case CondAllSet: diff --git a/cpp_src/core/nsselecter/comparator/comparator_indexed.h b/cpp_src/core/nsselecter/comparator/comparator_indexed.h index e4563ab0a..6d6e0f77a 100644 --- a/cpp_src/core/nsselecter/comparator/comparator_indexed.h +++ b/cpp_src/core/nsselecter/comparator/comparator_indexed.h @@ -11,8 +11,11 @@ #include "core/payload/payloadfieldvalue.h" #include "core/payload/payloadtype.h" #include "core/payload/payloadvalue.h" +#include "estl/fast_hash_map.h" +#include "estl/fast_hash_set.h" #include "helpers.h" #include "tools/string_regexp_functions.h" +#include "vendor/sparse-map/sparse_set.h" namespace reindexer { @@ -48,6 +51,15 @@ struct ValuesHolder { }; }; +// TODO: fast_hash_map here somehow causes crashes in the centos 7 asan CI builds +template +using LowSparsityMap = tsl::sparse_map, std::allocator>, + tsl::sh::power_of_two_growth_policy<2>, tsl::sh::exception_safety::basic, tsl::sh::sparsity::low>; + +template +using LowSparsitySet = tsl::sparse_set, std::allocator, tsl::sh::power_of_two_growth_policy<2>, + tsl::sh::exception_safety::basic, tsl::sh::sparsity::low>; + template struct ValuesHolder { using Type = std::pair; @@ -61,9 +73,19 @@ struct ValuesHolder { }; }; +template <> +struct ValuesHolder { + using Type = LowSparsitySet>; +}; + +template <> +struct ValuesHolder { + using Type = LowSparsitySet>; +}; + template struct ValuesHolder { - using Type = std::unordered_set; + using Type = LowSparsitySet>; }; template <> @@ -73,14 +95,30 @@ struct ValuesHolder { template <> struct ValuesHolder { - using Type = unordered_payload_set; + using Type = unordered_payload_ref_set; +}; + +template <> +struct ValuesHolder { + struct Type { + LowSparsityMap> values_; + fast_hash_set allSetValues_; + }; +}; + +template <> +struct ValuesHolder { + struct Type { + LowSparsityMap> values_; + fast_hash_set allSetValues_; + }; }; template struct ValuesHolder { struct Type { - std::unordered_map values_; - std::unordered_set allSetValues_; + LowSparsityMap> values_; + fast_hash_set allSetValues_; }; }; @@ -88,7 +126,7 @@ template <> struct ValuesHolder { struct Type { key_string_map values_; - std::unordered_set allSetValues_; + fast_hash_set allSetValues_; }; }; @@ -96,7 +134,7 @@ template <> struct ValuesHolder { struct Type { unordered_payload_map values_; - std::unordered_set allSetValues_; + fast_hash_set allSetValues_; }; }; @@ -1729,57 +1767,71 @@ class ComparatorIndexed { switch (impl_.index()) { case 0: res = std::get_if<0>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 1: res = std::get_if<1>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 2: res = std::get_if<2>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 3: res = std::get_if<3>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 4: res = std::get_if<4>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 5: res = std::get_if<5>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 6: res = std::get_if<6>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 7: res = std::get_if<7>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 8: res = std::get_if<8>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 9: res = std::get_if<9>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 10: res = std::get_if<10>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 11: res = std::get_if<11>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 12: res = std::get_if<12>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 13: res = std::get_if<13>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 14: res = std::get_if<14>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 15: res = std::get_if<15>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; default: abort(); } - matchedCount_ += res; - return res; } void ClearDistinctValues() noexcept { std::visit([](auto& impl) { impl.ClearDistinctValues(); }, impl_); @@ -1809,54 +1861,67 @@ template <> switch (impl_.index()) { case 0: res = std::get_if<0>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 1: res = std::get_if<1>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 2: res = std::get_if<2>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 3: res = std::get_if<3>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 4: res = std::get_if<4>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 5: res = std::get_if<5>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 6: res = std::get_if<6>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 7: res = std::get_if<7>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 8: res = std::get_if<8>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 9: res = std::get_if<9>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 10: res = std::get_if<10>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 11: res = std::get_if<11>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 12: res = std::get_if<12>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 13: res = std::get_if<13>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 14: res = std::get_if<14>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; default: abort(); } - matchedCount_ += res; - return res; } template <> @@ -1874,24 +1939,27 @@ template <> switch (impl_.index()) { case 0: res = std::get_if<0>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 1: res = std::get_if<1>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 2: res = std::get_if<2>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 3: res = std::get_if<3>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; case 4: res = std::get_if<4>(&impl_)->Compare(item, rowId); - break; + matchedCount_ += res; + return res; default: abort(); } - matchedCount_ += res; - return res; } extern template std::string ComparatorIndexed::ConditionStr() const; diff --git a/cpp_src/core/nsselecter/comparator/equalposition_comparator.cc b/cpp_src/core/nsselecter/comparator/equalposition_comparator.cc index e0df999e4..c28eb467a 100644 --- a/cpp_src/core/nsselecter/comparator/equalposition_comparator.cc +++ b/cpp_src/core/nsselecter/comparator/equalposition_comparator.cc @@ -28,7 +28,7 @@ void EqualPositionComparator::bindField(const std::string &name, F field, const ctx.cmpString.SetValues(cond, values); ctx.cmpDouble.SetValues(cond, values); ctx.cmpUuid.SetValues(cond, values); - assertrx(ctx_.size() == fields_.size()); + assertrx_throw(ctx_.size() == fields_.size()); name_ += ' ' + name; } @@ -46,7 +46,7 @@ bool EqualPositionComparator::Compare(const PayloadValue &pv, IdType /*rowId*/) if (isRegularIndex) { pl.Get(fields_[j], v); } else { - assertrx(tagsPathIdx < fields_.getTagsPathsLength()); + assertrx_throw(tagsPathIdx < fields_.getTagsPathsLength()); pl.GetByJsonPath(fields_.getTagsPath(tagsPathIdx++), v, KeyValueType::Undefined{}); } if (v.size() < len) len = vals.back().size(); @@ -55,7 +55,7 @@ bool EqualPositionComparator::Compare(const PayloadValue &pv, IdType /*rowId*/) for (size_t i = 0; i < len; ++i) { bool cmpRes = true; for (size_t j = 0; j < fields_.size(); ++j) { - assertrx(i < vals[j].size()); + assertrx_throw(i < vals[j].size()); cmpRes &= !vals[j][i].Type().Is() && compareField(j, vals[j][i]); if (!cmpRes) break; } diff --git a/cpp_src/core/nsselecter/comparator/equalposition_comparator_impl.h b/cpp_src/core/nsselecter/comparator/equalposition_comparator_impl.h index 1ea3e5cac..2805a424a 100644 --- a/cpp_src/core/nsselecter/comparator/equalposition_comparator_impl.h +++ b/cpp_src/core/nsselecter/comparator/equalposition_comparator_impl.h @@ -78,7 +78,7 @@ class EqualPositionComparatorImpl { } void ClearAllSetValues() { - assertrx(allSetValuesS_); + assertrx_throw(allSetValuesS_); allSetValuesS_->clear(); } @@ -181,7 +181,7 @@ class EqualPositionComparatorImpl { std::abort(); } void ClearAllSetValues() { - assertrx(allSetValuesS_); + assertrx_throw(allSetValuesS_); allSetValuesS_->clear(); } diff --git a/cpp_src/core/nsselecter/comparator/fieldscomparator.cc b/cpp_src/core/nsselecter/comparator/fieldscomparator.cc index d558ab9a5..e0f191aef 100644 --- a/cpp_src/core/nsselecter/comparator/fieldscomparator.cc +++ b/cpp_src/core/nsselecter/comparator/fieldscomparator.cc @@ -18,7 +18,7 @@ class ArrayAdapter { return *this; } bool operator!=(const ConstIterator &other) const noexcept { - assertrx(&aa_ == &other.aa_); + assertrx_dbg(&aa_ == &other.aa_); return index_ != other.index_; } reindexer::Variant operator*() const { return aa_[index_]; } @@ -33,7 +33,7 @@ class ArrayAdapter { : ptr_{ptr}, len_{l}, sizeof_{size_of}, type_{t} {} size_t size() const noexcept { return len_; } reindexer::Variant operator[](size_t i) const { - assertrx(i < len_); + assertrx_dbg(i < len_); return type_.EvaluateOneOf( [&](reindexer::KeyValueType::Int64) noexcept { return reindexer::Variant{*reinterpret_cast(ptr_ + sizeof_ * i)}; diff --git a/cpp_src/core/nsselecter/comparator/fieldscomparator.h b/cpp_src/core/nsselecter/comparator/fieldscomparator.h index a58e0469e..e8f90730d 100644 --- a/cpp_src/core/nsselecter/comparator/fieldscomparator.h +++ b/cpp_src/core/nsselecter/comparator/fieldscomparator.h @@ -47,7 +47,7 @@ class FieldsComparator { leftFieldSet = true; } void SetRightField(const FieldsSet& fields) { - assertrx(leftFieldSet); + assertrx_dbg(leftFieldSet); setField(fields, ctx_[0].rCtx_); } void SetLeftField(const FieldsSet& fset, KeyValueType type, bool isArray) { @@ -61,7 +61,7 @@ class FieldsComparator { leftFieldSet = true; } void SetRightField(const FieldsSet& fset, KeyValueType type, bool isArray) { - assertrx(leftFieldSet); + assertrx_dbg(leftFieldSet); if ((ctx_.size() > 1) != type.Is()) { throw Error{errQueryExec, "A composite index cannot be compared with a non-composite one: %s", name_}; } @@ -92,8 +92,8 @@ class FieldsComparator { void setField(const TagsPath& tpath, FieldContext& fctx) { fctx.fields_.push_back(tpath); } void setField(const FieldsSet& fields, FieldContext& fctx) { - assertrx_throw(fields.size() == 1); - assertrx_throw(fields[0] == IndexValueType::SetByJsonPath); + assertrx_dbg(fields.size() == 1); + assertrx_dbg(fields[0] == IndexValueType::SetByJsonPath); setField(fields.getTagsPath(0), fctx); } void setField(FieldContext& fctx, FieldsSet fset, KeyValueType type, bool isArray) { @@ -120,7 +120,7 @@ class FieldsComparator { validateTypes(ctx_[i].lCtx_.type_, ctx_[i].rCtx_.type_); } } else { - assertrx(tagsPathIdx < fields.getTagsPathsLength()); + assertrx_dbg(tagsPathIdx < fields.getTagsPathsLength()); setField(fields.getTagsPath(tagsPathIdx++), left ? ctx_[i].lCtx_ : ctx_[i].rCtx_); } } diff --git a/cpp_src/core/nsselecter/explaincalc.cc b/cpp_src/core/nsselecter/explaincalc.cc index 27c1e1151..9cd5544f0 100644 --- a/cpp_src/core/nsselecter/explaincalc.cc +++ b/cpp_src/core/nsselecter/explaincalc.cc @@ -93,6 +93,58 @@ constexpr std::string_view fieldKind(IteratorFieldKind fk) { } } +RX_NO_INLINE static std::string buildPreselectDescription(const JoinPreResult &result) { + assertrx_throw(result.properties); + return std::visit( + overloaded{ + [&](const IdSet &) -> std::string { + const PreselectProperties &props = *result.properties; + switch (result.storedValuesOptStatus) { + case StoredValuesOptimizationStatus::DisabledByCompositeIndex: + return fmt::sprintf( + "using preselected_rows, because joined query contains composite index condition in the ON-clause and " + "joined query's expected max iterations count of %d is less than max_iterations_idset_preresult limit of %d", + props.qresMaxIteratios, props.maxIterationsIdSetPreResult); + case StoredValuesOptimizationStatus::DisabledByFullTextIndex: + return fmt::sprintf( + "using preselected_rows, because joined query contains fulltext index condition in the ON-clause and joined " + "query's expected max iterations count of %d is less than max_iterations_idset_preresult limit of %d", + props.qresMaxIteratios, props.maxIterationsIdSetPreResult); + case StoredValuesOptimizationStatus::DisabledByJoinedFieldSort: + return fmt::sprintf( + "using preselected_rows, because sort by joined field was requested and joined query's " + "expected max iterations count of %d is less than max_iterations_idset_preresult limit of %d", + props.qresMaxIteratios, props.maxIterationsIdSetPreResult); + case StoredValuesOptimizationStatus::Enabled: + return fmt::sprintf( + "using preselected_rows, because joined query's expected max iterations count of %d is less than " + "max_iterations_idset_preresult limit of %d and larger then max copied values count of %d", + props.qresMaxIteratios, props.maxIterationsIdSetPreResult, + JoinedSelector::MaxIterationsForPreResultStoreValuesOptimization()); + default: + throw_as_assert; + } + }, + [&](const SelectIteratorContainer &) -> std::string { + const PreselectProperties &props = *result.properties; + if (props.isLimitExceeded) { + return fmt::sprintf( + "using no_preselect, because joined query's expected max iterations count of %d is larger than " + "max_iterations_idset_preresult limit of %d", + props.qresMaxIteratios, props.maxIterationsIdSetPreResult); + } else if (props.isUnorderedIndexSort) { + return "using no_preselect, because there is a sorted query on an unordered index"; + } + return "using no_preselect, because joined query expects a sort a btree index that is not yet committed " + "(optimization of indexes for the target namespace is not complete)"; + }, + [&](const JoinPreResult::Values &) { + return fmt::sprintf("using preselected_values, because the namespace's max iterations count is very small of %d", + result.properties->qresMaxIteratios); + }}, + result.payload); +} + static std::string addToJSON(JsonBuilder &builder, const JoinedSelector &js, OpType op = OpAnd) { using namespace std::string_view_literals; auto jsonSel = builder.Object(); @@ -117,10 +169,13 @@ static std::string addToJSON(JsonBuilder &builder, const JoinedSelector &js, OpT jsonSel.Put("method"sv, "no_preselect"sv); jsonSel.Put("keys"sv, iterators.Size()); }}, - js.PreResult().preselectedPayload); + js.PreResult().payload); if (!js.PreResult().explainPreSelect.empty()) { jsonSel.Raw("explain_preselect"sv, js.PreResult().explainPreSelect); } + if (js.PreResult().properties) { + jsonSel.Put("description"sv, buildPreselectDescription(js.PreResult())); + } if (!js.ExplainOneSelect().empty()) { jsonSel.Raw("explain_select"sv, js.ExplainOneSelect()); } @@ -204,9 +259,9 @@ std::string ExplainCalc::GetJSON() { json.Put("loop_us"sv, To_us(loop_)); json.Put("general_sort_us"sv, To_us(sort_)); if (!subqueries_.empty()) { - auto subQuries = json.Array("subqueries"); + auto subQueries = json.Array("subqueries"); for (const auto &sq : subqueries_) { - auto s = subQuries.Object(); + auto s = subQueries.Object(); s.Put("namespace", sq.NsName()); s.Raw("explain", sq.Explain()); std::visit(overloaded{[&](size_t k) { s.Put("keys", k); }, [&](const std::string &f) { s.Put("field", f); }}, @@ -281,7 +336,7 @@ std::string SelectIteratorContainer::explainJSON(const_iterator begin, const_ite name << opName(it->operation, it == begin) << siter.name; }, [&](const JoinSelectIterator &jiter) { - assertrx(jiter.joinIndex < jselectors->size()); + assertrx_throw(jiter.joinIndex < jselectors->size()); const std::string jName{addToJSON(builder, (*jselectors)[jiter.joinIndex], it->operation)}; name << opName(it->operation, it == begin) << jName; }, @@ -316,9 +371,9 @@ std::string SelectIteratorContainer::explainJSON(const_iterator begin, const_ite name << opName(it->operation, it == begin) << c.Name(); }), [&](const AlwaysTrue &) { - auto jsonSkiped = builder.Object(); - jsonSkiped.Put("type"sv, "Skipped"sv); - jsonSkiped.Put("description"sv, "always "s + (it->operation == OpNot ? "false" : "true")); + auto jsonSkipped = builder.Object(); + jsonSkipped.Put("type"sv, "Skipped"sv); + jsonSkipped.Put("description"sv, "always "s + (it->operation == OpNot ? "false" : "true")); name << opName(it->operation == OpNot ? OpAnd : it->operation, it == begin) << "Always"sv << (it->operation == OpNot ? "False"sv : "True"sv); }); diff --git a/cpp_src/core/nsselecter/explaincalc.h b/cpp_src/core/nsselecter/explaincalc.h index 0e8e1a165..02cce38d1 100644 --- a/cpp_src/core/nsselecter/explaincalc.h +++ b/cpp_src/core/nsselecter/explaincalc.h @@ -46,7 +46,7 @@ class ExplainCalc { public: ExplainCalc() = default; - ExplainCalc(bool enable) noexcept : enabled_(enable) {} + explicit ExplainCalc(bool enable) noexcept : enabled_(enable) {} void StartTiming() noexcept { if (enabled_) lap(); @@ -76,12 +76,12 @@ class ExplainCalc { void PutCount(int cnt) noexcept { count_ = cnt; } void PutSortIndex(std::string_view index) noexcept { sortIndex_ = index; } - void PutSelectors(const SelectIteratorContainer* qres) noexcept { selectors_ = qres; } - void PutJoinedSelectors(const JoinedSelectors* jselectors) noexcept { jselectors_ = jselectors; } + void PutSelectors(const SelectIteratorContainer *qres) noexcept { selectors_ = qres; } + void PutJoinedSelectors(const JoinedSelectors *jselectors) noexcept { jselectors_ = jselectors; } void SetPreselectTime(Duration preselectTime) noexcept { preselect_ = preselectTime; } - void PutOnConditionInjections(const OnConditionInjections* onCondInjections) noexcept { onInjections_ = onCondInjections; } + void PutOnConditionInjections(const OnConditionInjections *onCondInjections) noexcept { onInjections_ = onCondInjections; } void SetSortOptimization(bool enable) noexcept { sortOptimization_ = enable; } - void SetSubQueriesExplains(std::vector&& subQueriesExpl) noexcept { subqueries_ = std::move(subQueriesExpl); } + void SetSubQueriesExplains(std::vector &&subQueriesExpl) noexcept { subqueries_ = std::move(subQueriesExpl); } void LogDump(int logLevel); std::string GetJSON(); @@ -149,7 +149,7 @@ struct ConditionInjection { std::string initCond; ///< single condition from Join ON section. SQL-like string ExplainCalc::Duration totalTime_ = ExplainCalc::Duration::zero(); ///< total time elapsed from injection attempt start till the end of substitution or rejection - std::string explain; ///< optoinal{JoinOnInjection.type == Select}. Explain raw string from Select subquery. + std::string explain; ///< optional{JoinOnInjection.type == Select}. Explain raw string from Select subquery. AggType aggType = AggType::AggUnknown; ///< aggregation type used in subquery bool succeed = false; ///< result of injection attempt std::string_view reason; ///< optional{succeed==false}. Explains condition injection failure diff --git a/cpp_src/core/nsselecter/itemcomparator.cc b/cpp_src/core/nsselecter/itemcomparator.cc index 9af216707..5872e1bea 100644 --- a/cpp_src/core/nsselecter/itemcomparator.cc +++ b/cpp_src/core/nsselecter/itemcomparator.cc @@ -145,7 +145,7 @@ void ItemComparator::bindOne(const SortingContext::Entry &sortingEntry, Inserter } else { assertrx_dbg(&(*ctx_.joinedSelectors)[e.nsIdx] == jns.joinedSelector); } - assertrx_throw(!std::holds_alternative(jns.joinedSelector->PreResult().preselectedPayload)); + assertrx_dbg(!std::holds_alternative(jns.joinedSelector->PreResult().payload)); const auto &ns = *jns.joinedSelector->RightNs(); const int fieldIdx = e.index; if (fieldIdx == IndexValueType::SetByJsonPath || ns.indexes_[fieldIdx]->Opts().IsSparse()) { @@ -309,11 +309,8 @@ ComparationResult ItemComparator::compareFields(IdType lId, IdType rId, size_t & return std::make_pair(Variant(*(static_cast(rawData) + lId)), Variant(*(static_cast(rawData) + rId))); }, - [](OneOf) noexcept - -> std::pair { - assertrx(0); - abort(); - }); + [](OneOf) + -> std::pair { throw_as_assert; }); cmpRes = values.first.template Compare(values.second, opts ? *opts : CollateOpts()); } else { cmpRes = ConstPayload(ns_.payloadType_, ns_.items_[lId]) diff --git a/cpp_src/core/nsselecter/joinedselector.cc b/cpp_src/core/nsselecter/joinedselector.cc index 06311437f..602b2591b 100644 --- a/cpp_src/core/nsselecter/joinedselector.cc +++ b/cpp_src/core/nsselecter/joinedselector.cc @@ -11,12 +11,12 @@ constexpr size_t kMaxIterationsScaleForInnerJoinOptimization = 100; namespace reindexer { void JoinedSelector::selectFromRightNs(LocalQueryResults &joinItemR, const Query &query, bool &found, bool &matchedAtLeastOnce) { - assertrx(rightNs_); + assertrx_dbg(rightNs_); JoinCacheRes joinResLong; rightNs_->getFromJoinCache(query, joinQuery_, joinResLong); - rightNs_->getIndsideFromJoinCache(joinRes_); + rightNs_->getInsideFromJoinCache(joinRes_); if (joinRes_.needPut) { rightNs_->putToJoinCache(joinRes_, preSelectCtx_.ResultPtr()); } @@ -54,11 +54,11 @@ void JoinedSelector::selectFromPreResultValues(LocalQueryResults &joinItemR, con bool &matchedAtLeastOnce) const { size_t matched = 0; const auto &entries = query.Entries(); - const JoinPreResult::Values &values = std::get(PreResult().preselectedPayload); + const JoinPreResult::Values &values = std::get(PreResult().payload); const auto &pt = values.payloadType; for (const ItemRef &item : values) { auto &v = item.Value(); - assertrx(!v.IsFree()); + assertrx_throw(!v.IsFree()); if (entries.CheckIfSatisfyConditions({pt, v})) { if (++matched > query.Limit()) break; found = true; @@ -105,7 +105,7 @@ bool JoinedSelector::Process(IdType rowId, int nsId, ConstPayload payload, bool overloaded{[&](const JoinPreResult::Values &) { selectFromPreResultValues(joinItemR, *itemQueryPtr, found, matchedAtLeastOnce); }, Restricted{}( [&](const auto &) { selectFromRightNs(joinItemR, *itemQueryPtr, found, matchedAtLeastOnce); })}, - PreResult().preselectedPayload); + PreResult().payload); if (match && found) { assertrx_throw(nsId < static_cast(result_.joined_.size())); joins::NamespaceResults &nsJoinRes = result_.joined_[nsId]; @@ -124,7 +124,8 @@ VariantArray JoinedSelector::readValuesOfRightNsFrom(const Cont &data, const Fn const auto leftFieldType = entry.LeftFieldType(); VariantArray res; if (rightFieldType.Is()) { - unordered_payload_set set(data.size(), hash_composite(pt, entry.RightFields()), equal_composite(pt, entry.RightFields())); + unordered_payload_ref_set set(data.size(), hash_composite_ref(pt, entry.RightFields()), + equal_composite_ref(pt, entry.RightFields())); for (const auto &v : data) { const auto pl = createPayload(v); if (!pl.Value()->IsFree()) { @@ -137,8 +138,8 @@ VariantArray JoinedSelector::readValuesOfRightNsFrom(const Cont &data, const Fn } } else { tsl::sparse_set set(data.size()); - for (const auto &v : data) { - const auto pl = createPayload(v); + for (const auto &val : data) { + const auto pl = createPayload(val); if (pl.Value()->IsFree()) { continue; } @@ -158,7 +159,7 @@ VariantArray JoinedSelector::readValuesOfRightNsFrom(const Cont &data, const Fn } VariantArray JoinedSelector::readValuesFromPreResult(const QueryJoinEntry &entry) const { - const JoinPreResult::Values &values = std::get(PreResult().preselectedPayload); + const JoinPreResult::Values &values = std::get(PreResult().payload); return readValuesOfRightNsFrom( values, [&values](const ItemRef &item) noexcept { @@ -174,11 +175,11 @@ void JoinedSelector::AppendSelectIteratorOfJoinIndexData(SelectIteratorContainer Restricted{}([maxIterations](const auto &v) noexcept { return v.size() > *maxIterations * kMaxIterationsScaleForInnerJoinOptimization; })}, - PreResult().preselectedPayload)) { + PreResult().payload)) { return; } unsigned optimized = 0; - assertrx_throw(!std::holds_alternative(PreResult().preselectedPayload) || + assertrx_throw(!std::holds_alternative(PreResult().payload) || itemQuery_.Entries().Size() == joinQuery_.joinEntries_.size()); for (size_t i = 0; i < joinQuery_.joinEntries_.size(); ++i) { const QueryJoinEntry &joinEntry = joinQuery_.joinEntries_[i]; @@ -188,7 +189,7 @@ void JoinedSelector::AppendSelectIteratorOfJoinIndexData(SelectIteratorContainer continue; } const auto &leftIndex = leftNs_->indexes_[joinEntry.LeftIdxNo()]; - assertrx(!IsFullText(leftIndex->Type())); + assertrx_throw(!IsFullText(leftIndex->Type())); if (leftIndex->Opts().IsSparse()) continue; const VariantArray values = std::visit(overloaded{[&](const IdSet &preselected) { @@ -200,13 +201,10 @@ void JoinedSelector::AppendSelectIteratorOfJoinIndexData(SelectIteratorContainer joinEntry, rightNs_->payloadType_); }, [&](const JoinPreResult::Values &) { return readValuesFromPreResult(joinEntry); }, - [](const SelectIteratorContainer &) -> VariantArray { - assertrx_throw(0); - abort(); - }}, - PreResult().preselectedPayload); + [](const SelectIteratorContainer &) -> VariantArray { throw_as_assert; }}, + PreResult().payload); auto ctx = selectFnc ? selectFnc->CreateCtx(joinEntry.LeftIdxNo()) : BaseFunctionCtx::Ptr{}; - assertrx(!ctx || ctx->type != BaseFunctionCtx::kFtCtx); + assertrx_throw(!ctx || ctx->type != BaseFunctionCtx::kFtCtx); if (leftIndex->Opts().GetCollateMode() == CollateUTF8) { for (auto &key : values) key.EnsureUTF8(); diff --git a/cpp_src/core/nsselecter/joinedselector.h b/cpp_src/core/nsselecter/joinedselector.h index 15724ba95..113fae7b9 100644 --- a/cpp_src/core/nsselecter/joinedselector.h +++ b/cpp_src/core/nsselecter/joinedselector.h @@ -1,4 +1,5 @@ #pragma once +#include #include "core/joincache.h" #include "core/namespace/namespaceimpl.h" #include "explaincalc.h" @@ -6,11 +7,22 @@ namespace reindexer { +struct PreselectProperties { + PreselectProperties(int64_t qresMaxIts, int64_t maxItersIdSetPreResult) noexcept + : qresMaxIteratios{qresMaxIts}, maxIterationsIdSetPreResult{maxItersIdSetPreResult} {} + + bool isLimitExceeded = false; + bool isUnorderedIndexSort = false; + bool btreeIndexOptimizationEnabled = false; + int64_t qresMaxIteratios; + const int64_t maxIterationsIdSetPreResult; +}; + struct JoinPreResult { class Values : public std::vector { public: Values(const PayloadType &pt, const TagsMatcher &tm) noexcept : payloadType{pt}, tagsMatcher{tm} {} - Values(Values &&other) + Values(Values &&other) noexcept : std::vector(std::move(other)), payloadType(std::move(other.payloadType)), tagsMatcher(std::move(other.tagsMatcher)), @@ -26,7 +38,7 @@ struct JoinPreResult { for (size_t i = 0; i < size(); ++i) Payload{payloadType, (*this)[i].Value()}.ReleaseStrings(); } } - bool Locked() const { return locked_; } + bool Locked() const noexcept { return locked_; } void Lock() { assertrx_throw(!locked_); for (size_t i = 0; i < size(); ++i) Payload{payloadType, (*this)[i].Value()}.AddRefStrings(); @@ -37,18 +49,21 @@ struct JoinPreResult { PayloadType payloadType; TagsMatcher tagsMatcher; + NamespaceName nsName; private: bool locked_ = false; bool preselectAllowed_ = true; }; + using PreselectT = std::variant; typedef std::shared_ptr Ptr; typedef std::shared_ptr CPtr; - std::variant preselectedPayload; + PreselectT payload; bool enableSortOrders = false; bool btreeIndexOptimizationEnabled = true; - bool enableStoredValues = false; + StoredValuesOptimizationStatus storedValuesOptStatus = StoredValuesOptimizationStatus::Enabled; + std::optional properties; std::string explainPreSelect; }; @@ -74,12 +89,12 @@ class JoinPreResultExecuteCtx { const JoinPreResult &Result() const & noexcept { return *result_; } JoinPreSelectMode Mode() const noexcept { return mode_; } int MainQueryMaxIterations() const { - assertrx_throw(mode_ == JoinPreSelectMode::ForInjection); + assertrx_dbg(mode_ == JoinPreSelectMode::ForInjection); return mainQueryMaxIterations_; } const JoinPreResult::CPtr &ResultPtr() const & noexcept { return result_; } void Reject() { - assertrx_throw(mode_ == JoinPreSelectMode::ForInjection); + assertrx_dbg(mode_ == JoinPreSelectMode::ForInjection); mode_ = JoinPreSelectMode::InjectionRejected; } @@ -108,8 +123,7 @@ class JoinedSelector { public: JoinedSelector(JoinType joinType, NamespaceImpl::Ptr leftNs, NamespaceImpl::Ptr rightNs, JoinCacheRes &&joinRes, Query &&itemQuery, LocalQueryResults &result, const JoinedQuery &joinQuery, JoinPreResultExecuteCtx &&preSelCtx, uint32_t joinedFieldIdx, - SelectFunctionsHolder &selectFunctions, uint32_t joinedSelectorsCount, bool inTransaction, int64_t lastUpdateTime, - const RdxContext &rdxCtx) + SelectFunctionsHolder &selectFunctions, bool inTransaction, int64_t lastUpdateTime, const RdxContext &rdxCtx) : joinType_(joinType), called_(0), matched_(0), @@ -122,7 +136,6 @@ class JoinedSelector { preSelectCtx_(std::move(preSelCtx)), joinedFieldIdx_(joinedFieldIdx), selectFunctions_(selectFunctions), - joinedSelectorsCount_(joinedSelectorsCount), rdxCtx_(rdxCtx), optimized_(false), inTransaction_{inTransaction}, @@ -181,7 +194,6 @@ class JoinedSelector { std::string explainOneSelect_; uint32_t joinedFieldIdx_; SelectFunctionsHolder &selectFunctions_; - uint32_t joinedSelectorsCount_; const RdxContext &rdxCtx_; bool optimized_ = false; bool inTransaction_ = false; diff --git a/cpp_src/core/nsselecter/nsselecter.cc b/cpp_src/core/nsselecter/nsselecter.cc index 846db2c28..864aa4066 100644 --- a/cpp_src/core/nsselecter/nsselecter.cc +++ b/cpp_src/core/nsselecter/nsselecter.cc @@ -15,7 +15,6 @@ using namespace std::string_view_literals; constexpr int kMinIterationsForInnerJoinOptimization = 100; -constexpr int kMaxIterationsForIdsetPreresult = 20000; constexpr int kCancelCheckFrequency = 1024; namespace reindexer { @@ -47,7 +46,7 @@ void NsSelecter::operator()(LocalQueryResults &result, SelectCtxWithJoinPreSelec bool needPutCachedTotal = false; const auto initTotalCount = result.totalCount; const bool containAggCount = containSomeAggCount(AggCount); - const bool containAggCountCached = containAggCount ? false : containSomeAggCount(AggCountCached); + const bool containAggCountCached = !containAggCount && containSomeAggCount(AggCountCached); bool needCalcTotal = aggregationQueryRef.CalcTotal() == ModeAccurateTotal || containAggCount; QueryCacheKey ckey; @@ -140,14 +139,14 @@ void NsSelecter::operator()(LocalQueryResults &result, SelectCtxWithJoinPreSelec ctx.isForceAll = isForceAll; if constexpr (std::is_same_v) { - // all futher queries for this join MUST have the same enableSortOrders flag + // all further queries for this join MUST have the same enableSortOrders flag ctx.preSelect.Result().enableSortOrders = ctx.sortingContext.enableSortOrders; } else if constexpr (std::is_same_v) { // If in current join query sort orders are disabled // then preResult query also MUST have disabled flag // If assert fails, then possible query has unlock ns - // or ns->sortOrdersFlag_ has been reseted under read lock! - if (!ctx.sortingContext.enableSortOrders) assertrx_throw(!ctx.preSelect.Result().enableSortOrders); + // or ns->sortOrdersFlag_ was reset under read lock! + assertrx_throw(ctx.sortingContext.enableSortOrders || !ctx.preSelect.Result().enableSortOrders); ctx.sortingContext.enableSortOrders = ctx.preSelect.Result().enableSortOrders; } @@ -157,18 +156,18 @@ void NsSelecter::operator()(LocalQueryResults &result, SelectCtxWithJoinPreSelec prepareSortingContext(sortBy, ctx, isFt, qPreproc.AvailableSelectBySortIndex()); if (ctx.sortingContext.isOptimizationEnabled()) { - // Unbuilt btree index optimization is available for query with + // Un-built btree index optimization is available for query with // Check, is it really possible to use it if (isFt || // Disabled if there are search results (!std::is_same_v /*&& !ctx.preSelect.Result().btreeIndexOptimizationEnabled*/) || // Disabled in join preresult (TMP: - // now disable for all right + void> /*&& !ctx.preSelect.Result().btreeIndexOptimizationEnabled*/) || // Disabled in join pre-result + // (TMP: now disable for all right // queries), TODO: enable right // queries) (qPreproc.Size() && qPreproc.GetQueryEntries().GetOperation(0) == OpNot) || // Not in first condition - !isSortOptimizatonEffective(qPreproc.GetQueryEntries(), ctx, - rdxCtx) // Optimization is not effective (e.g. query contains more effecive filters) + !isSortOptimizationEffective(qPreproc.GetQueryEntries(), ctx, + rdxCtx) // Optimization is not effective (e.g. query contains more effective filters) ) { ctx.sortingContext.resetOptimization(); ctx.isForceAll = true; @@ -192,8 +191,8 @@ void NsSelecter::operator()(LocalQueryResults &result, SelectCtxWithJoinPreSelec qres.Append(iterators.begin(), iterators.end()); } }, - [](const JoinPreResult::Values &) { assertrx_throw(0); }}, - ctx.preSelect.Result().preselectedPayload); + [](const JoinPreResult::Values &) { throw_as_assert; }}, + ctx.preSelect.Result().payload); } qres.PrepareIteratorsForSelectLoop(qPreproc, ctx.sortingContext.sortId(), isFt, *ns_, fnc_, ft_ctx_, rdxCtx); @@ -202,31 +201,39 @@ void NsSelecter::operator()(LocalQueryResults &result, SelectCtxWithJoinPreSelec int maxIterations = qres.GetMaxIterations(); if constexpr (std::is_same_v) { - // Building pre result for next joins - static_assert(kMaxIterationsForIdsetPreresult > JoinedSelector::MaxIterationsForPreResultStoreValuesOptimization(), ""); - if (ctx.preSelect.Result().enableStoredValues && - qres.GetMaxIterations(true) <= JoinedSelector::MaxIterationsForPreResultStoreValuesOptimization()) { - ctx.preSelect.Result().preselectedPayload.template emplace(ns_->payloadType_, ns_->tagsMatcher_); - // Return preResult as QueryIterators if: - } else if (maxIterations >= kMaxIterationsForIdsetPreresult || // 1. We have > QueryIterator which expects more than - // 20000 iterations. - (ctx.sortingContext.entries.size() && - !ctx.sortingContext.sortIndex()) // 2. We have sorted query, by unordered index - || ctx.preSelect.Result().btreeIndexOptimizationEnabled) { // 3. We have btree-index that is not committed yet - ctx.preSelect.Result().preselectedPayload.template emplace(); - std::get(ctx.preSelect.Result().preselectedPayload).Append(qres.cbegin(), qres.cend()); - if rx_unlikely (logLevel >= LogInfo) { - logPrintf(LogInfo, "Built preResult (expected %d iterations) with %d iterators, q='%s'", maxIterations, qres.Size(), - ctx.query.GetSQL()); - } - return; + // Building pre-result for next joins + auto &preResult = ctx.preSelect.Result(); + preResult.properties.emplace(qres.GetMaxIterations(true), ns_->config().maxIterationsIdSetPreResult); + auto &preselectProps = preResult.properties.value(); + assertrx_throw(preselectProps.maxIterationsIdSetPreResult > JoinedSelector::MaxIterationsForPreResultStoreValuesOptimization()); + if ((preResult.storedValuesOptStatus == StoredValuesOptimizationStatus::Enabled) && + preselectProps.qresMaxIteratios <= JoinedSelector::MaxIterationsForPreResultStoreValuesOptimization()) { + preResult.payload.template emplace(ns_->payloadType_, ns_->tagsMatcher_); } else { - // Build preResult as single IdSet - ctx.preSelect.Result().preselectedPayload.template emplace(); - // For building join preresult always use ASC sort orders - for (SortingEntry &se : sortBy) se.desc = false; + preselectProps.isLimitExceeded = (maxIterations >= preselectProps.maxIterationsIdSetPreResult); + preselectProps.isUnorderedIndexSort = + !preselectProps.isLimitExceeded && (ctx.sortingContext.entries.size() && !ctx.sortingContext.sortIndex()); + preselectProps.btreeIndexOptimizationEnabled = preResult.btreeIndexOptimizationEnabled; + preselectProps.qresMaxIteratios = maxIterations; + // Return pre-result as QueryIterators if: + if (preselectProps.isLimitExceeded || // 1. We have > QueryIterator which expects more than configured max iterations. + preselectProps.isUnorderedIndexSort || // 2. We have sorted query, by unordered index + preselectProps.btreeIndexOptimizationEnabled) { // 3. We have btree-index that is not committed yet + preResult.payload.template emplace(); + std::get(preResult.payload).Append(qres.cbegin(), qres.cend()); + if rx_unlikely (logLevel >= LogInfo) { + logPrintf(LogInfo, "Built preResult (expected %d iterations) with %d iterators, q='%s'", + preselectProps.qresMaxIteratios, qres.Size(), ctx.query.GetSQL()); + } + return; + } else { // Build pre-result as single IdSet + preResult.payload.template emplace(); + // For building join pre-result always use ASC sort orders + for (SortingEntry &se : sortBy) se.desc = false; + } } - } else if constexpr (std::is_same_v) { + } // pre-select rejected + else if constexpr (std::is_same_v) { if (ctx.preSelect.Mode() == JoinPreSelectMode::ForInjection && maxIterations > long(ctx.preSelect.MainQueryMaxIterations()) * ctx.preSelect.MainQueryMaxIterations()) { ctx.preSelect.Reject(); @@ -264,7 +271,7 @@ void NsSelecter::operator()(LocalQueryResults &result, SelectCtxWithJoinPreSelec SelectKeyResult scan; if (ctx.sortingContext.isOptimizationEnabled()) { auto it = ns_->indexes_[ctx.sortingContext.uncommitedIndex]->CreateIterator(); - maxIterations = ns_->ItemsCount(); + maxIterations = ns_->itemsCount(); it->SetMaxIterations(maxIterations); scan.emplace_back(std::move(it)); } else { @@ -284,24 +291,23 @@ void NsSelecter::operator()(LocalQueryResults &result, SelectCtxWithJoinPreSelec // Get maximum iterations count, for right calculation comparators costs qres.SortByCost(maxIterations); - // Check idset must be 1st + // Check IdSet must be 1st qres.CheckFirstQuery(); // Rewind all results iterators - qres.VisitForEach( - Skip{}, - Restricted>{}( - [](auto &comp) { comp.ClearDistinctValues(); }), - [reverse, maxIterations](SelectIterator &it) { it.Start(reverse, maxIterations); }); + qres.VisitForEach(Skip{}, + Restricted>{}( + [](auto &comp) { comp.ClearDistinctValues(); }), + [reverse, maxIterations](SelectIterator &it) { it.Start(reverse, maxIterations); }); - // Let iterators choose most effecive algorith + // Let iterators choose most efficient algorithm assertrx_throw(qres.Size()); qres.SetExpectMaxIterations(maxIterations); explain.AddPostprocessTime(); - // do not calc total by loop, if we have only 1 condition with 1 idset + // do not calc total by loop, if we have only 1 condition with 1 IdSet lctx.calcTotal = needCalcTotal && (hasComparators || qPreproc.MoreThanOneEvaluation() || qres.Size() > 1 || qres.Get(0).size() > 1); @@ -334,12 +340,12 @@ void NsSelecter::operator()(LocalQueryResults &result, SelectCtxWithJoinPreSelec selectLoop(lctx, result, rdxCtx); } - // Get total count for simple query with 1 condition and 1 idset + // Get total count for simple query with 1 condition and 1 IdSet if (needCalcTotal && !lctx.calcTotal) { if (!ctx.query.Entries().Empty()) { result.totalCount += qres.Get(0).GetMaxIterations(); } else { - result.totalCount += ns_->ItemsCount(); + result.totalCount += ns_->itemsCount(); } } } @@ -360,7 +366,7 @@ void NsSelecter::operator()(LocalQueryResults &result, SelectCtxWithJoinPreSelec } }, Restricted{}([](const auto &) {})}, - ctx.preSelect.Result().preselectedPayload); + ctx.preSelect.Result().payload); } else { for (size_t i = resultInitSize; i < result.Items().size(); ++i) { auto &iref = result.Items()[i]; @@ -375,15 +381,15 @@ void NsSelecter::operator()(LocalQueryResults &result, SelectCtxWithJoinPreSelec result.aggregationResults.push_back(aggregator.GetResult()); } } - // Put count/count_cached to aggretions + // Put count/count_cached to aggregations if (aggregationQueryRef.HasCalcTotal() || containAggCount || containAggCountCached) { AggregationResult ret; ret.fields = {"*"}; ret.type = (aggregationQueryRef.CalcTotal() == ModeAccurateTotal || containAggCount) ? AggCount : AggCountCached; if (ctx.isMergeQuerySubQuery()) { - assertrx_throw(!result.aggregationResults.empty()); + assertrx_dbg(!result.aggregationResults.empty()); auto &agg = result.aggregationResults.back(); - assertrx_throw(agg.type == ret.type); + assertrx_dbg(agg.type == ret.type); agg.SetValue(result.totalCount); } else { ret.SetValue(result.totalCount); @@ -398,11 +404,8 @@ void NsSelecter::operator()(LocalQueryResults &result, SelectCtxWithJoinPreSelec if constexpr (std::is_same_v) { explain.PutCount(std::visit(overloaded{[](const IdSet &ids) noexcept -> size_t { return ids.size(); }, [](const JoinPreResult::Values &values) noexcept { return values.size(); }, - [](const SelectIteratorContainer &) -> size_t { - assertrx_throw(0); - abort(); - }}, - ctx.preSelect.Result().preselectedPayload)); + [](const SelectIteratorContainer &) -> size_t { throw_as_assert; }}, + ctx.preSelect.Result().payload)); } else { explain.PutCount(result.Count()); } @@ -440,8 +443,8 @@ void NsSelecter::operator()(LocalQueryResults &result, SelectCtxWithJoinPreSelec logPrintf(LogInfo, "Built values preResult (expected %d iterations) with %d values, q = '%s'", explain.Iterations(), values.size(), ctx.query.GetSQL()); }, - [](const SelectIteratorContainer &) { assertrx_throw(0); }}, - ctx.preSelect.Result().preselectedPayload); + [](const SelectIteratorContainer &) { throw_as_assert; }}, + ctx.preSelect.Result().payload); } } } @@ -462,7 +465,7 @@ const PayloadValue &getValue(const ItemRef &ite template <> class NsSelecter::MainNsValueGetter { public: - MainNsValueGetter(const NamespaceImpl &ns) noexcept : ns_{ns} {} + explicit MainNsValueGetter(const NamespaceImpl &ns) noexcept : ns_{ns} {} const PayloadValue &Value(const ItemRef &itemRef) const noexcept { return ns_.items_[itemRef.Id()]; } ConstPayload Payload(const ItemRef &itemRef) const noexcept { return ConstPayload{ns_.payloadType_, Value(itemRef)}; } @@ -473,7 +476,7 @@ class NsSelecter::MainNsValueGetter { template <> class NsSelecter::MainNsValueGetter { public: - MainNsValueGetter(const NamespaceImpl &ns) noexcept : ns_{ns} {} + explicit MainNsValueGetter(const NamespaceImpl &ns) noexcept : ns_{ns} {} const PayloadValue &Value(const ItemRef &itemRef) const noexcept { return itemRef.Value(); } ConstPayload Payload(const ItemRef &itemRef) const noexcept { return ConstPayload{ns_.payloadType_, Value(itemRef)}; } @@ -614,13 +617,13 @@ class ForcedSortMap { template class ForcedMapInserter { public: - ForcedMapInserter(Map &m) noexcept : map_{m} {} + explicit ForcedMapInserter(Map &m) noexcept : map_{m} {} template void Insert(V &&value) { if (const auto [iter, success] = map_.emplace(std::forward(value), cost_); success) { ++cost_; } else if (iter->second != cost_ - 1) { - static constexpr auto errMsg = "Forced sort value '%s' is dublicated. Deduplicated by the first occurrence."; + static constexpr auto errMsg = "Forced sort value '%s' is duplicated. Deduplicated by the first occurrence."; if constexpr (std::is_same_v) { logPrintf(LogInfo, errMsg, value.template As()); } else { @@ -842,12 +845,8 @@ void NsSelecter::processLeftJoins(LocalQueryResults &qr, SelectCtx &sctx, size_t bool NsSelecter::checkIfThereAreLeftJoins(SelectCtx &sctx) const { if (!sctx.joinedSelectors) return false; - for (auto &joinedSelector : *sctx.joinedSelectors) { - if (joinedSelector.Type() == JoinType::LeftJoin) { - return true; - } - } - return false; + return std::any_of(sctx.joinedSelectors->begin(), sctx.joinedSelectors->end(), + [](const auto &selector) { return selector.Type() == JoinType::LeftJoin; }); } template @@ -908,7 +907,7 @@ void NsSelecter::selectLoop(LoopCtx &ctx, ResultsT &result, co size_t initCount = 0; if constexpr (!kPreprocessingBeforFT) { if constexpr (!std::is_same_v) { - if (auto *values = std::get_if(&sctx.preSelect.Result().preselectedPayload); values) { + if (auto *values = std::get_if(&sctx.preSelect.Result().payload); values) { initCount = values->size(); } else { initCount = resultSize(result); @@ -922,11 +921,11 @@ void NsSelecter::selectLoop(LoopCtx &ctx, ResultsT &result, co ctx.count = ctx.qPreproc.Count(); } - // reserve queryresults, if we have only 1 condition with 1 idset + // reserve query results, if we have only 1 condition with 1 idset if (qres.Size() == 1 && qres.IsSelectIterator(0) && qres.Get(0).size() == 1) { const unsigned reserve = std::min(unsigned(qres.Get(0).GetMaxIterations()), ctx.count); if constexpr (std::is_same_v) { - if (auto *values = std::get_if(&sctx.preSelect.Result().preselectedPayload); values) { + if (auto *values = std::get_if(&sctx.preSelect.Result().payload); values) { values->reserve(reserve + initCount); } else { resultReserve(result, initCount + reserve); @@ -1034,7 +1033,7 @@ void NsSelecter::selectLoop(LoopCtx &ctx, ResultsT &result, co if constexpr (!kPreprocessingBeforFT) { bool toPreResultValues = false; if constexpr (std::is_same_v) { - if (auto values = std::get_if(&sctx.preSelect.Result().preselectedPayload); values) { + if (auto values = std::get_if(&sctx.preSelect.Result().payload); values) { if (sctx.isForceAll) { assertrx_throw(!ctx.qPreproc.Start() || !initCount); if (ctx.qPreproc.Start() <= values->size()) { @@ -1109,10 +1108,8 @@ void NsSelecter::getSortIndexValue(const SortingContext &sortCtx, IdType rowId, return Variant(p_string(static_cast(e.rawData.ptr) + rowId), Variant::no_hold_t{}); }, [&e, rowId](KeyValueType::Uuid) noexcept { return Variant(*(static_cast(e.rawData.ptr) + rowId)); }, - [](OneOf) noexcept - -> Variant { - assertrx(0); - abort(); + [](OneOf) -> Variant { + throw_as_assert; })}; } else { ConstPayload pv(ns_->payloadType_, ns_->items_[rowId]); @@ -1159,8 +1156,8 @@ void NsSelecter::addSelectResult(uint8_t proc, IdType rowId, IdType properRowId, values.emplace_back(properRowId, ns_->items_[properRowId], proc, sctx.nsid); } }, - [](const SelectIteratorContainer &) { assertrx_throw(0); }}, - sctx.preSelect.Result().preselectedPayload); + [](const SelectIteratorContainer &) { throw_as_assert; }}, + sctx.preSelect.Result().payload); } else { if (!sctx.sortingContext.expressions.empty()) { result.Add({properRowId, sctx.sortingContext.exprResults[0].size(), proc, sctx.nsid}); @@ -1178,9 +1175,11 @@ void NsSelecter::addSelectResult(uint8_t proc, IdType rowId, IdType properRowId, } } -void NsSelecter::checkStrictModeAgg(StrictMode strictMode, const std::string &name, const std::string &nsName, +void NsSelecter::checkStrictModeAgg(StrictMode strictMode, std::string_view name, const NamespaceName &nsName, const TagsMatcher &tagsMatcher) const { - if (int index = IndexValueType::SetByJsonPath; ns_->tryGetIndexByName(name, index)) return; + if (int index = IndexValueType::SetByJsonPath; ns_->tryGetIndexByName(name, index)) { + return; + } if (strictMode == StrictModeIndexes) { throw Error(errParams, @@ -1257,7 +1256,7 @@ h_vector NsSelecter::getAggregators(const std::vector const PayloadType & { return values.payloadType; }, - Restricted{}( - [&js](const auto &) noexcept -> const PayloadType & { return js.rightNs_->payloadType_; })}, - js.PreResult().preselectedPayload) - .FieldByName(column, index); - if (index == IndexValueType::SetByJsonPath) { - skipSortingEntry |= !validateField( - strictMode, column, js.joinQuery_.NsName(), - std::visit(overloaded{[](const JoinPreResult::Values &values) noexcept -> const TagsMatcher & { return values.tagsMatcher; }, - Restricted{}( - [&js](const auto &) noexcept -> const TagsMatcher & { return js.rightNs_->tagsMatcher_; })}, - js.PreResult().preselectedPayload)); - } + std::visit(overloaded{[&](const JoinPreResult::Values &values) noexcept { + values.payloadType.FieldByName(column, index); + if (index == IndexValueType::SetByJsonPath) { + skipSortingEntry |= !validateField(strictMode, column, values.nsName, values.tagsMatcher); + } + }, + Restricted{}([&](const auto &) noexcept { + js.rightNs_->payloadType_.FieldByName(column, index); + if (index == IndexValueType::SetByJsonPath) { + skipSortingEntry |= !validateField(strictMode, column, js.rightNs_->name_, js.rightNs_->tagsMatcher_); + } + })}, + js.PreResult().payload); } -bool NsSelecter::validateField(StrictMode strictMode, std::string_view name, std::string_view nsName, const TagsMatcher &tagsMatcher) { +bool NsSelecter::validateField(StrictMode strictMode, std::string_view name, const NamespaceName &nsName, const TagsMatcher &tagsMatcher) { if (strictMode == StrictModeIndexes) { throw Error(errStrictMode, "Current query strict mode allows sort by index fields only. There are no indexes with name '%s' in namespace '%s'", @@ -1360,7 +1359,7 @@ void NsSelecter::prepareSortingContext(SortingEntries &sortBy, SelectCtx &ctx, b } else if (expr.ByJoinedField()) { auto &je{expr.GetJoinedIndex()}; const auto &js = joinedSelectors[je.nsIdx]; - assertrx_throw(!std::holds_alternative(js.PreResult().preselectedPayload)); + assertrx_throw(!std::holds_alternative(js.PreResult().payload)); bool skip{false}; int jeIndex = IndexValueType::SetByJsonPath; prepareSortIndex(*js.RightNs(), je.column, jeIndex, skip, strictMode); @@ -1438,7 +1437,7 @@ enum class CostCountingPolicy : bool { Any, ExceptTargetSortIdxSeq }; template class CostCalculator { public: - CostCalculator(size_t _totalCost) noexcept : totalCost_(_totalCost) {} + explicit CostCalculator(size_t _totalCost) noexcept : totalCost_(_totalCost) {} void BeginSequence() noexcept { isInSequence_ = true; hasInappositeEntries_ = false; @@ -1447,9 +1446,7 @@ class CostCalculator { } void EndSequence() noexcept { if (isInSequence_ && !hasInappositeEntries_) { - if constexpr (countingPolicy == CostCountingPolicy::Any) { - totalCost_ = std::min(curCost_, totalCost_); - } else if (!onlyTargetSortIdxInSequence_) { + if ((countingPolicy == CostCountingPolicy::Any) || !onlyTargetSortIdxInSequence_) { totalCost_ = std::min(curCost_, totalCost_); } } @@ -1525,15 +1522,15 @@ class CostCalculator { }; size_t NsSelecter::calculateNormalCost(const QueryEntries &qentries, SelectCtx &ctx, const RdxContext &rdxCtx) { - const size_t totalItemsCount = ns_->ItemsCount(); + const size_t totalItemsCount = ns_->itemsCount(); CostCalculator costCalculator(totalItemsCount); enum { SortIndexNotFound = 0, SortIndexFound, SortIndexHasUnorderedConditions } sortIndexSearchState = SortIndexNotFound; for (size_t next, i = 0, sz = qentries.Size(); i != sz; i = next) { next = qentries.Next(i); const bool calculateEntry = costCalculator.OnNewEntry(qentries, i, next); qentries.Visit( - i, [] RX_PRE_LMBD_ALWAYS_INLINE(const SubQueryEntry &) RX_POST_LMBD_ALWAYS_INLINE { assertrx_throw(0); }, - [] RX_PRE_LMBD_ALWAYS_INLINE(const SubQueryFieldEntry &) RX_POST_LMBD_ALWAYS_INLINE { assertrx_throw(0); }, + i, [] RX_PRE_LMBD_ALWAYS_INLINE(const SubQueryEntry &) RX_POST_LMBD_ALWAYS_INLINE { throw_as_assert; }, + [] RX_PRE_LMBD_ALWAYS_INLINE(const SubQueryFieldEntry &) RX_POST_LMBD_ALWAYS_INLINE { throw_as_assert; }, Skip{}, [&costCalculator] RX_PRE_LMBD_ALWAYS_INLINE(const QueryEntriesBracket &) RX_POST_LMBD_ALWAYS_INLINE noexcept { costCalculator.MarkInapposite(); }, @@ -1581,8 +1578,8 @@ size_t NsSelecter::calculateNormalCost(const QueryEntries &qentries, SelectCtx & opts.inTransaction = ctx.inTransaction; try { - SelectKeyResults reslts = index->SelectKey(qe.Values(), qe.Condition(), 0, opts, nullptr, rdxCtx); - costCalculator.Add(reslts, qe.IndexNo() == ctx.sortingContext.uncommitedIndex); + SelectKeyResults results = index->SelectKey(qe.Values(), qe.Condition(), 0, opts, nullptr, rdxCtx); + costCalculator.Add(results, qe.IndexNo() == ctx.sortingContext.uncommitedIndex); } catch (const Error &) { costCalculator.MarkInapposite(); } @@ -1606,8 +1603,8 @@ size_t NsSelecter::calculateOptimizedCost(size_t costNormal, const QueryEntries } qentries.Visit( i, Skip{}, - [] RX_PRE_LMBD_ALWAYS_INLINE(const SubQueryEntry &) RX_POST_LMBD_ALWAYS_INLINE { assertrx_throw(0); }, - [] RX_PRE_LMBD_ALWAYS_INLINE(const SubQueryFieldEntry &) RX_POST_LMBD_ALWAYS_INLINE { assertrx_throw(0); }, + [] RX_PRE_LMBD_ALWAYS_INLINE(const SubQueryEntry &) RX_POST_LMBD_ALWAYS_INLINE { throw_as_assert; }, + [] RX_PRE_LMBD_ALWAYS_INLINE(const SubQueryFieldEntry &) RX_POST_LMBD_ALWAYS_INLINE { throw_as_assert; }, [&costCalculator] RX_PRE_LMBD_ALWAYS_INLINE(const QueryEntriesBracket &) RX_POST_LMBD_ALWAYS_INLINE noexcept { costCalculator.MarkInapposite(); }, [&costCalculator] RX_PRE_LMBD_ALWAYS_INLINE(const JoinQueryEntry &) @@ -1621,15 +1618,16 @@ size_t NsSelecter::calculateOptimizedCost(size_t costNormal, const QueryEntries } Index::SelectOpts opts; - opts.itemsCountInNamespace = ns_->ItemsCount(); + opts.itemsCountInNamespace = ns_->itemsCount(); opts.disableIdSetCache = 1; opts.unbuiltSortOrders = 1; opts.indexesNotOptimized = !ctx.sortingContext.enableSortOrders; opts.inTransaction = ctx.inTransaction; try { - SelectKeyResults reslts = ns_->indexes_[qe.IndexNo()]->SelectKey(qe.Values(), qe.Condition(), 0, opts, nullptr, rdxCtx); - costCalculator.Add(reslts); + SelectKeyResults results = + ns_->indexes_[qe.IndexNo()]->SelectKey(qe.Values(), qe.Condition(), 0, opts, nullptr, rdxCtx); + costCalculator.Add(results); } catch (const Error &) { costCalculator.MarkInapposite(); } @@ -1639,7 +1637,7 @@ size_t NsSelecter::calculateOptimizedCost(size_t costNormal, const QueryEntries return costCalculator.TotalCost(); } -bool NsSelecter::isSortOptimizatonEffective(const QueryEntries &qentries, SelectCtx &ctx, const RdxContext &rdxCtx) { +bool NsSelecter::isSortOptimizationEffective(const QueryEntries &qentries, SelectCtx &ctx, const RdxContext &rdxCtx) { if (qentries.Size() == 0) { return true; } @@ -1654,8 +1652,8 @@ bool NsSelecter::isSortOptimizatonEffective(const QueryEntries &qentries, Select if (expectedMaxIterationsNormal == 0) { return false; } - const size_t totalItemsCount = ns_->ItemsCount(); - const size_t costNormal = size_t(double(expectedMaxIterationsNormal) * log2(expectedMaxIterationsNormal)); + const size_t totalItemsCount = ns_->itemsCount(); + const auto costNormal = size_t(double(expectedMaxIterationsNormal) * log2(expectedMaxIterationsNormal)); if (costNormal >= totalItemsCount) { // Check if it's more effective to iterate over all the items via btree, than select and sort ids via the most effective index return true; @@ -1674,7 +1672,7 @@ bool NsSelecter::isSortOptimizatonEffective(const QueryEntries &qentries, Select } } if (!ctx.isForceAll && ctx.query.HasLimit()) { - // If optimization will be disabled, selecter will must to iterate over all the results, ignoring limit + // If optimization will be disabled, selector will must iterate over all the results, ignoring limit // Experimental value. It was chosen during debugging request from issue #1402. // TODO: It's possible to evaluate this multiplier, based on the query conditions, but the only way to avoid corner cases is to // allow user to hint this optimization. @@ -1739,7 +1737,7 @@ void NsSelecter::writeAggregationResultMergeSubQuery(LocalQueryResults &result, case AggCount: case AggCountCached: case AggUnknown: - assertrx_throw(false); + throw_as_assert; } } } diff --git a/cpp_src/core/nsselecter/nsselecter.h b/cpp_src/core/nsselecter/nsselecter.h index aaa52a2c2..29638abfb 100644 --- a/cpp_src/core/nsselecter/nsselecter.h +++ b/cpp_src/core/nsselecter/nsselecter.h @@ -113,9 +113,9 @@ class NsSelecter { size_t calculateNormalCost(const QueryEntries &qe, SelectCtx &ctx, const RdxContext &rdxCtx); size_t calculateOptimizedCost(size_t costNormal, const QueryEntries &qe, SelectCtx &ctx, const RdxContext &rdxCtx); - bool isSortOptimizatonEffective(const QueryEntries &qe, SelectCtx &ctx, const RdxContext &rdxCtx); - static bool validateField(StrictMode strictMode, std::string_view name, std::string_view nsName, const TagsMatcher &tagsMatcher); - void checkStrictModeAgg(StrictMode strictMode, const std::string &name, const std::string &nsName, + bool isSortOptimizationEffective(const QueryEntries &qe, SelectCtx &ctx, const RdxContext &rdxCtx); + static bool validateField(StrictMode strictMode, std::string_view name, const NamespaceName& nsName, const TagsMatcher &tagsMatcher); + void checkStrictModeAgg(StrictMode strictMode, std::string_view name, const NamespaceName& nsName, const TagsMatcher &tagsMatcher) const; void writeAggregationResultMergeSubQuery(LocalQueryResults &result, h_vector &aggregators, SelectCtx &ctx); diff --git a/cpp_src/core/nsselecter/querypreprocessor.cc b/cpp_src/core/nsselecter/querypreprocessor.cc index 335cb1f75..4d9ed00d3 100644 --- a/cpp_src/core/nsselecter/querypreprocessor.cc +++ b/cpp_src/core/nsselecter/querypreprocessor.cc @@ -151,7 +151,7 @@ int QueryPreprocessor::calculateMaxIterations(const size_t from, const size_t to } Index::SelectOpts opts; - opts.itemsCountInNamespace = ns_.ItemsCount(); + opts.itemsCountInNamespace = ns_.itemsCount(); opts.disableIdSetCache = 1; opts.unbuiltSortOrders = 0; opts.indexesNotOptimized = !enableSortOrders; @@ -172,14 +172,7 @@ int QueryPreprocessor::calculateMaxIterations(const size_t from, const size_t to }, [maxMaxIters](const BetweenFieldsQueryEntry &) noexcept { return maxMaxIters; }, [maxMaxIters](const JoinQueryEntry &) noexcept { return maxMaxIters; }, - [](const SubQueryEntry &) -> int { - assertrx_throw(0); - abort(); - }, - [](const SubQueryFieldEntry &) -> int { - assertrx_throw(0); - abort(); - }, + [](const SubQueryEntry &) -> int { throw_as_assert; }, [](const SubQueryFieldEntry &) -> int { throw_as_assert; }, [maxMaxIters](const AlwaysTrue &) noexcept { return maxMaxIters; }, [&](const AlwaysFalse &) noexcept { return 0; })); switch (GetOperation(cur)) { case OpAnd: @@ -207,7 +200,7 @@ void QueryPreprocessor::InjectConditionsFromJoins(JoinedSelectors &js, OnConditi bool inTransaction, bool enableSortOrders, const RdxContext &rdxCtx) { h_vector maxIterations(Size()); span maxItersSpan(maxIterations.data(), maxIterations.size()); - const int maxIters = calculateMaxIterations(0, Size(), ns_.ItemsCount(), maxItersSpan, inTransaction, enableSortOrders, rdxCtx); + const int maxIters = calculateMaxIterations(0, Size(), ns_.itemsCount(), maxItersSpan, inTransaction, enableSortOrders, rdxCtx); const bool needExplain = query_.NeedExplain() || logLevel >= LogInfo; if (needExplain) { injectConditionsFromJoins(0, Size(), js, expalainOnInjections, maxIters, maxIterations, inTransaction, @@ -216,7 +209,7 @@ void QueryPreprocessor::InjectConditionsFromJoins(JoinedSelectors &js, OnConditi injectConditionsFromJoins(0, Size(), js, expalainOnInjections, maxIters, maxIterations, inTransaction, enableSortOrders, rdxCtx); } - assertrx_throw(maxIterations.size() == Size()); + assertrx_dbg(maxIterations.size() == Size()); } bool QueryPreprocessor::removeAlwaysFalse() { @@ -412,15 +405,9 @@ size_t QueryPreprocessor::lookupQueryIndexes(uint16_t dst, uint16_t srcBegin, ui size_t merged = 0; for (size_t src = srcBegin, nextSrc; src < srcEnd; src = nextSrc) { nextSrc = Next(src); - const MergeResult mergeResult = container_[src].Visit( - [](const SubQueryEntry &) -> MergeResult { - assertrx_throw(0); - abort(); - }, - [](const SubQueryFieldEntry &) -> MergeResult { - assertrx_throw(0); - abort(); - }, + const auto mergeResult = container_[src].Visit( + [](const SubQueryEntry &) -> MergeResult { throw_as_assert; }, + [](const SubQueryFieldEntry &) -> MergeResult { throw_as_assert; }, [&](const QueryEntriesBracket &) { if (dst != src) container_[dst] = std::move(container_[src]); const size_t mergedInBracket = lookupQueryIndexes(dst + 1, src + 1, nextSrc); @@ -430,7 +417,7 @@ size_t QueryPreprocessor::lookupQueryIndexes(uint16_t dst, uint16_t srcBegin, ui }, [&](QueryEntry &entry) { if (entry.IsFieldIndexed()) { - // try merge entries with AND opetator + // try to merge entries with AND operator if ((GetOperation(src) == OpAnd) && (nextSrc >= srcEnd || GetOperation(nextSrc) != OpOr)) { if (size_t(entry.IndexNo()) >= iidx.size()) { const auto oldSize = iidx.size(); @@ -441,8 +428,13 @@ size_t QueryPreprocessor::lookupQueryIndexes(uint16_t dst, uint16_t srcBegin, ui const Index &index = *ns_.indexes_[entry.IndexNo()]; const auto &indexOpts = index.Opts(); if (iidxRef > 0 && !indexOpts.IsArray()) { - switch (mergeQueryEntries(iidxRef - 1, src, index.IsOrdered() ? MergeOrdered::Yes : MergeOrdered::No, - indexOpts.collateOpts_)) { + const auto orderedFlag = index.IsOrdered() ? MergeOrdered::Yes : MergeOrdered::No; + const auto mergeRes = IsComposite(index.Type()) + ? mergeQueryEntries( + iidxRef - 1, src, orderedFlag, ns_.payloadType_, index.Fields()) + : mergeQueryEntries(iidxRef - 1, src, orderedFlag, + indexOpts.collateOpts_); + switch (mergeRes) { case MergeResult::NotMerged: break; case MergeResult::Merged: @@ -494,7 +486,7 @@ void QueryPreprocessor::CheckUniqueFtQuery() const { bool found = false; VisitForEach( Skip{}, - [](const SubQueryEntry &) { assertrx_throw(0); }, [](const SubQueryFieldEntry &) { assertrx_throw(0); }, + [](const SubQueryEntry &) { throw_as_assert; }, [](const SubQueryFieldEntry &) { throw_as_assert; }, [&](const QueryEntry &qe) { if (qe.IsFieldIndexed() && IsFullText(ns_.indexes_[qe.IndexNo()]->Type())) { if (found) { @@ -523,7 +515,7 @@ const std::vector *QueryPreprocessor::getCompositeIndex(int field) const no return nullptr; } -static void createCompositeKeyValues(const span> &values, Payload &pl, VariantArray &ret, +static void createCompositeKeyValues(span> values, Payload &pl, VariantArray &ret, uint32_t resultSetSize, uint32_t n) { const auto &v = values[n]; for (auto it = v.second.cbegin(), end = v.second.cend(); it != end; ++it) { @@ -540,7 +532,7 @@ static void createCompositeKeyValues(const span> &v } } -static VariantArray createCompositeKeyValues(const span> &values, const PayloadType &plType, +static VariantArray createCompositeKeyValues(span> values, const PayloadType &plType, uint32_t resultSetSize) { PayloadValue d(plType.TotalSize()); Payload pl(plType, d); @@ -603,7 +595,7 @@ size_t QueryPreprocessor::substituteCompositeIndexes(const size_t from, const si } constexpr static CompositeValuesCountLimits kCompositeSetLimits; if (resultSetSize != maxSetSize) { - // Do not perform substitution if result set size becoms larger than initial indexes set size + // Do not perform substitution if result set size becomes larger than initial indexes set size // and this size is greater than limit // TODO: This is potential customization point for the user's hints system if (resultSetSize > kCompositeSetLimits[res.entries.size()]) { @@ -625,7 +617,7 @@ size_t QueryPreprocessor::substituteCompositeIndexes(const size_t from, const si setQueryIndex(fld, res.idx, ns_); container_[first].Emplace(std::move(fld), qValues.size() == 1 ? CondEq : CondSet, std::move(qValues)); } - deleteRanges.Add(span(res.entries.data() + 1, res.entries.size() - 1)); + deleteRanges.Add(span(res.entries.data() + 1, res.entries.size() - 1)); resIdx = searcher.RemoveUsedAndGetNext(resIdx); } for (auto rit = deleteRanges.rbegin(); rit != deleteRanges.rend(); ++rit) { @@ -638,8 +630,8 @@ size_t QueryPreprocessor::substituteCompositeIndexes(const size_t from, const si void QueryPreprocessor::initIndexedQueries(size_t begin, size_t end) { for (auto cur = begin; cur != end; cur = Next(cur)) { Visit( - cur, Skip{}, [](const SubQueryEntry &) { assertrx_throw(0); }, - [](const SubQueryFieldEntry &) { assertrx_throw(0); }, + cur, Skip{}, [](const SubQueryEntry &) { throw_as_assert; }, + [](const SubQueryFieldEntry &) { throw_as_assert; }, [this, cur](const QueryEntriesBracket &) { initIndexedQueries(cur + 1, Next(cur)); }, [this](BetweenFieldsQueryEntry &entry) { if (!entry.FieldsHaveBeenSet()) { @@ -659,17 +651,17 @@ void QueryPreprocessor::initIndexedQueries(size_t begin, size_t end) { } } -[[nodiscard]] SortingEntries QueryPreprocessor::detectOptimalSortOrder() const { +SortingEntries QueryPreprocessor::detectOptimalSortOrder() const { if (!AvailableSelectBySortIndex()) return {}; if (const Index *maxIdx = findMaxIndex(cbegin(), cend())) { SortingEntries sortingEntries; sortingEntries.emplace_back(maxIdx->Name(), false); return sortingEntries; } - return SortingEntries(); + return {}; } -[[nodiscard]] const Index *QueryPreprocessor::findMaxIndex(QueryEntries::const_iterator begin, QueryEntries::const_iterator end) const { +const Index *QueryPreprocessor::findMaxIndex(QueryEntries::const_iterator begin, QueryEntries::const_iterator end) const { thread_local h_vector foundIndexes; foundIndexes.clear(); findMaxIndex(begin, end, foundIndexes); @@ -682,7 +674,7 @@ void QueryPreprocessor::initIndexedQueries(size_t begin, size_t end) { } return false; }); - if (foundIndexes.size() && foundIndexes[0].isFitForSortOptimization) { + if (!foundIndexes.empty() && foundIndexes[0].isFitForSortOptimization) { return foundIndexes[0].index; } return nullptr; @@ -691,35 +683,29 @@ void QueryPreprocessor::initIndexedQueries(size_t begin, size_t end) { void QueryPreprocessor::findMaxIndex(QueryEntries::const_iterator begin, QueryEntries::const_iterator end, h_vector &foundIndexes) const { for (auto it = begin; it != end; ++it) { - const FoundIndexInfo foundIdx = it->Visit( - [](const SubQueryEntry &) -> FoundIndexInfo { - assertrx_throw(0); - abort(); - }, - [](const SubQueryFieldEntry &) -> FoundIndexInfo { - assertrx_throw(0); - abort(); - }, - [this, &it, &foundIndexes](const QueryEntriesBracket &) { - findMaxIndex(it.cbegin(), it.cend(), foundIndexes); - return FoundIndexInfo(); - }, - [this](const QueryEntry &entry) -> FoundIndexInfo { - if (entry.IsFieldIndexed() && !entry.Distinct()) { - const auto idxPtr = ns_.indexes_[entry.IndexNo()].get(); - if (idxPtr->IsOrdered() && !idxPtr->Opts().IsArray()) { - if (IsOrderedCondition(entry.Condition())) { - return FoundIndexInfo{idxPtr, FoundIndexInfo::ConditionType::Compatible}; - } else if (entry.Condition() == CondAny || entry.Values().size() > 1) { - return FoundIndexInfo{idxPtr, FoundIndexInfo::ConditionType::Incompatible}; - } - } - } - return FoundIndexInfo(); - }, - [](const JoinQueryEntry &) noexcept { return FoundIndexInfo(); }, - [](const BetweenFieldsQueryEntry &) noexcept { return FoundIndexInfo(); }, - [](const AlwaysFalse &) noexcept { return FoundIndexInfo(); }, [](const AlwaysTrue &) noexcept { return FoundIndexInfo(); }); + const auto foundIdx = it->Visit([](const SubQueryEntry &) -> FoundIndexInfo { throw_as_assert; }, + [](const SubQueryFieldEntry &) -> FoundIndexInfo { throw_as_assert; }, + [this, &it, &foundIndexes](const QueryEntriesBracket &) { + findMaxIndex(it.cbegin(), it.cend(), foundIndexes); + return FoundIndexInfo(); + }, + [this](const QueryEntry &entry) -> FoundIndexInfo { + if (entry.IsFieldIndexed() && !entry.Distinct()) { + const auto idxPtr = ns_.indexes_[entry.IndexNo()].get(); + if (idxPtr->IsOrdered() && !idxPtr->Opts().IsArray()) { + if (IsOrderedCondition(entry.Condition())) { + return FoundIndexInfo{idxPtr, FoundIndexInfo::ConditionType::Compatible}; + } else if (entry.Condition() == CondAny || entry.Values().size() > 1) { + return FoundIndexInfo{idxPtr, FoundIndexInfo::ConditionType::Incompatible}; + } + } + } + return {}; + }, + [](const JoinQueryEntry &) noexcept { return FoundIndexInfo(); }, + [](const BetweenFieldsQueryEntry &) noexcept { return FoundIndexInfo(); }, + [](const AlwaysFalse &) noexcept { return FoundIndexInfo(); }, + [](const AlwaysTrue &) noexcept { return FoundIndexInfo(); }); if (foundIdx.index) { auto found = std::find_if(foundIndexes.begin(), foundIndexes.end(), [foundIdx](const FoundIndexInfo &i) { return i.index == foundIdx.index; }); @@ -732,9 +718,42 @@ void QueryPreprocessor::findMaxIndex(QueryEntries::const_iterator begin, QueryEn } } +namespace { + +class CompositeLess : less_composite_ref { +public: + CompositeLess(const PayloadType &type, const FieldsSet &fields) noexcept : less_composite_ref(type, fields) {} + + bool operator()(const Variant &lhs, const Variant &rhs) const { + assertrx_dbg(lhs.Type().Is()); + assertrx_dbg(rhs.Type().Is()); + return less_composite_ref::operator()(static_cast(lhs), static_cast(rhs)); + } +}; + +class CompositeEqual : equal_composite_ref { +public: + CompositeEqual(const PayloadType &type, const FieldsSet &fields) noexcept : equal_composite_ref(type, fields) {} + + bool operator()(const Variant &lhs, const Variant &rhs) const { + assertrx_dbg(lhs.Type().Is()); + assertrx_dbg(rhs.Type().Is()); + return equal_composite_ref::operator()(static_cast(lhs), static_cast(rhs)); + } +}; + +template +using MergeLessT = std::conditional_t; + +template +using MergeEqualT = std::conditional_t; + +} // namespace + constexpr size_t kMinArraySizeToUseHashSet = 250; +template QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesSetSet(QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position, - const CollateOpts &collate) { + const CmpArgs &...args) { // intersect 2 queryentries on the same index if rx_unlikely (lqe.Values().empty() || rqe.Values().empty()) { SetValue(position, AlwaysFalse{}); @@ -745,7 +764,7 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesSetSet(QueryE if (first.size() == 1) { const Variant &firstV = first[0]; - const Variant::EqualTo equalTo{collate}; + const MergeEqualT equalTo{args...}; for (const Variant &secondV : second) { if (equalTo(firstV, secondV)) { lqe.SetCondAndValues(CondEq, VariantArray{std::move(first[0])}); // NOLINT (bugprone-use-after-move) @@ -758,23 +777,36 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesSetSet(QueryE } else { VariantArray setValues; setValues.reserve(first.size()); - if (second.size() < kMinArraySizeToUseHashSet) { - // Intersect via binary search + sort for small vectors - boost::sort::pdqsort(first.begin(), first.end(), Variant::Less{collate}); - for (auto &&v : second) { - if (std::binary_search(first.begin(), first.end(), v, Variant::Less{collate})) { - setValues.emplace_back(std::move(v)); + if constexpr (vt == ValuesType::Scalar) { + if (second.size() < kMinArraySizeToUseHashSet) { + // Intersect via binary search + sort for small vectors + boost::sort::pdqsort(first.begin(), first.end(), MergeLessT{args...}); + for (auto &&v : second) { + if (std::binary_search(first.begin(), first.end(), v, MergeLessT{args...})) { + setValues.emplace_back(std::move(v)); + } + } + } else { + // Intersect via hash_set for large vectors + fast_hash_set_variant set{args...}; + set.reserve(first.size()); + for (auto &&v : first) { + set.emplace(std::move(v)); + } + for (auto &&v : second) { + if (set.erase(v)) { + setValues.emplace_back(std::move(v)); + } } } } else { - // Intersect via hash_set for large vectors - fast_hash_set_variant set{collate}; - set.reserve(first.size() * 2); - for (auto &&v : first) { - set.emplace(std::move(v)); + // Intersect via hash_set for the composite values + unordered_payload_ref_set set{first.size(), hash_composite_ref{args...}, equal_composite_ref{args...}}; + for (auto &v : first) { + set.emplace(static_cast(v)); } for (auto &&v : second) { - if (set.erase(v)) { + if (set.erase(static_cast(v))) { setValues.emplace_back(std::move(v)); } } @@ -789,14 +821,14 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesSetSet(QueryE } } -template -QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesAllSetSet(QueryEntry &allSet, QueryEntry &set, bool distinct, - size_t position, const CollateOpts &collate) { +template +QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesAllSetSet(NeedSwitch needSwitch, QueryEntry &allSet, QueryEntry &set, + bool distinct, size_t position, const CmpArgs &...args) { if rx_unlikely (allSet.Values().empty() || set.Values().empty()) { SetValue(position, AlwaysFalse{}); return MergeResult::Annihilated; } - const Variant::EqualTo equalTo{collate}; + const MergeEqualT equalTo{args...}; const auto lvIt = allSet.Values().begin(); const Variant &lv = *lvIt; for (auto it = lvIt + 1, endIt = allSet.Values().end(); it != endIt; ++it) { @@ -817,13 +849,14 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesAllSetSet(Que return MergeResult::Annihilated; } +template QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesAllSetAllSet(QueryEntry &lqe, QueryEntry &rqe, bool distinct, - size_t position, const CollateOpts &collate) { + size_t position, const CmpArgs &...args) { if rx_unlikely (lqe.Values().empty() || rqe.Values().empty()) { SetValue(position, AlwaysFalse{}); return MergeResult::Annihilated; } - const Variant::EqualTo equalTo{collate}; + const MergeEqualT equalTo{args...}; const auto lvIt = lqe.Values().begin(); const Variant &lv = *lvIt; for (auto it = lvIt + 1, endIt = lqe.Values().end(); it != endIt; ++it) { @@ -843,23 +876,23 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesAllSetAllSet( return MergeResult::Merged; } -template -QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesAny(QueryEntry &any, QueryEntry ¬Any, bool distinct, - size_t position) { +QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesAny(NeedSwitch needSwitch, QueryEntry &any, QueryEntry ¬Any, + bool distinct, size_t position) { if (notAny.Condition() == CondEmpty) { SetValue(position, AlwaysFalse{}); return MergeResult::Annihilated; } notAny.Distinct(distinct); - if constexpr (needSwitch == NeedSwitch::Yes) { + if (needSwitch == NeedSwitch::Yes) { any = std::move(notAny); } return MergeResult::Merged; } -template -QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesSetNotSet(QueryEntry &set, QueryEntry ¬Set, F filter, bool distinct, - size_t position, MergeOrdered mergeOrdered) { +template +QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesSetNotSet(NeedSwitch needSwitch, QueryEntry &set, QueryEntry ¬Set, + F filter, bool distinct, size_t position, + MergeOrdered mergeOrdered) { if rx_unlikely (set.Values().empty()) { SetValue(position, AlwaysFalse{}); return MergeResult::Annihilated; @@ -875,7 +908,7 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesSetNotSet(Que } if (mergeOrdered == MergeOrdered::No || set.Values().size() == 1) { set.Distinct(distinct); - if constexpr (needSwitch == NeedSwitch::Yes) { + if (needSwitch == NeedSwitch::Yes) { notSet = std::move(set); } return MergeResult::Merged; @@ -884,15 +917,15 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesSetNotSet(Que } } -template -QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesAllSetNotSet(QueryEntry &allSet, QueryEntry ¬Set, F filter, - bool distinct, size_t position, - const CollateOpts &collate) { +template +QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesAllSetNotSet(NeedSwitch needSwitch, QueryEntry &allSet, + QueryEntry ¬Set, F filter, bool distinct, + size_t position, const CmpArgs &...args) { if rx_unlikely (allSet.Values().empty()) { SetValue(position, AlwaysFalse{}); return MergeResult::Annihilated; } - const Variant::EqualTo equalTo{collate}; + const MergeEqualT equalTo{args...}; const auto lvIt = allSet.Values().begin(); const Variant &lv = *lvIt; for (auto it = lvIt + 1, endIt = allSet.Values().end(); it != endIt; ++it) { @@ -911,11 +944,12 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesAllSetNotSet( return MergeResult::Merged; } +template QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesLt(QueryEntry &lqe, QueryEntry &rqe, bool distinct, - const CollateOpts &collate) { + const CmpArgs &...args) { const Variant &lv = lqe.Values()[0]; const Variant &rv = rqe.Values()[0]; - const Variant::Less less{collate}; + const MergeLessT less{args...}; if (less(rv, lv)) { lqe.SetCondAndValues(rqe.Condition(), std::move(rqe).Values()); // NOLINT (bugprone-use-after-move) } else if (!less(lv, rv) && (lqe.Condition() != rqe.Condition())) { @@ -925,24 +959,26 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesLt(QueryEntry return MergeResult::Merged; } +template QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesGt(QueryEntry &lqe, QueryEntry &rqe, bool distinct, - const CollateOpts &collate) { + const CmpArgs &...args) { const Variant &lv = lqe.Values()[0]; const Variant &rv = rqe.Values()[0]; - if (Variant::Less{collate}(lv, rv)) { + if (MergeLessT{args...}(lv, rv)) { lqe.SetCondAndValues(rqe.Condition(), std::move(rqe).Values()); // NOLINT (bugprone-use-after-move) - } else if (Variant::EqualTo{collate}(lv, rv) && (lqe.Condition() != rqe.Condition())) { + } else if (MergeEqualT{args...}(lv, rv) && (lqe.Condition() != rqe.Condition())) { lqe.SetCondAndValues(CondGt, std::move(rqe).Values()); } lqe.Distinct(distinct); return MergeResult::Merged; } +template QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesLtGt(QueryEntry <, QueryEntry >, size_t position, - const CollateOpts &collate) { + const CmpArgs &...args) { const Variant <V = lt.Values()[0]; const Variant >V = gt.Values()[0]; - if (Variant::Less{collate}(gtV, ltV)) { + if (MergeLessT{args...}(gtV, ltV)) { return MergeResult::NotMerged; } else { SetValue(position, AlwaysFalse{}); @@ -950,14 +986,14 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesLtGt(QueryEnt } } -template -QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesLeGe(QueryEntry &le, QueryEntry &ge, bool distinct, size_t position, - const CollateOpts &collate) { +template +QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesLeGe(NeedSwitch needSwitch, QueryEntry &le, QueryEntry &ge, + bool distinct, size_t position, const CmpArgs &...args) { const Variant &leV = le.Values()[0]; const Variant &geV = ge.Values()[0]; - const Variant::Less less{collate}; - QueryEntry &target = needSwitch == NeedSwitch::No ? le : ge; - QueryEntry &source = needSwitch == NeedSwitch::No ? ge : le; + const MergeLessT less{args...}; + QueryEntry &target = needSwitch == NeedSwitch::Yes ? ge : le; + QueryEntry &source = needSwitch == NeedSwitch::Yes ? le : ge; if (less(leV, geV)) { SetValue(position, AlwaysFalse{}); return MergeResult::Annihilated; @@ -970,19 +1006,19 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesLeGe(QueryEnt return MergeResult::Merged; } -template -QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRangeLt(QueryEntry &range, QueryEntry <, bool distinct, - size_t position, const CollateOpts &collate) { +template +QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRangeLt(NeedSwitch needSwitch, QueryEntry &range, QueryEntry <, + bool distinct, size_t position, const CmpArgs &...args) { const Variant <V = lt.Values()[0]; const Variant &rngL = range.Values()[0]; const Variant &rngR = range.Values()[1]; - const Variant::Less less{collate}; + const MergeLessT less{args...}; if (!less(rngL, ltV)) { SetValue(position, AlwaysFalse{}); return MergeResult::Annihilated; } else if (less(rngR, ltV)) { range.Distinct(distinct); - if constexpr (needSwitch == NeedSwitch::Yes) { + if (needSwitch == NeedSwitch::Yes) { lt = std::move(range); } return MergeResult::Merged; @@ -991,19 +1027,19 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRangeLt(Query } } -template -QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRangeGt(QueryEntry &range, QueryEntry >, bool distinct, - size_t position, const CollateOpts &collate) { +template +QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRangeGt(NeedSwitch needSwitch, QueryEntry &range, QueryEntry >, + bool distinct, size_t position, const CmpArgs &...args) { const Variant >V = gt.Values()[0]; const Variant &rngL = range.Values()[0]; const Variant &rngR = range.Values()[1]; - const Variant::Less less{collate}; + const MergeLessT less{args...}; if (!less(gtV, rngR)) { SetValue(position, AlwaysFalse{}); return MergeResult::Annihilated; } else if (less(gtV, rngL)) { range.Distinct(distinct); - if constexpr (needSwitch == NeedSwitch::Yes) { + if (needSwitch == NeedSwitch::Yes) { gt = std::move(range); } return MergeResult::Merged; @@ -1012,18 +1048,18 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRangeGt(Query } } -template -QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRangeLe(QueryEntry &range, QueryEntry &le, bool distinct, - size_t position, const CollateOpts &collate) { +template +QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRangeLe(NeedSwitch needSwitch, QueryEntry &range, QueryEntry &le, + bool distinct, size_t position, const CmpArgs &...args) { const Variant &leV = le.Values()[0]; const Variant &rngL = range.Values()[0]; const Variant &rngR = range.Values()[1]; - const Variant::Less less{collate}; - QueryEntry &target = needSwitch == NeedSwitch::No ? range : le; + const MergeLessT less{args...}; + QueryEntry &target = needSwitch == NeedSwitch::Yes ? le : range; if (less(leV, rngL)) { SetValue(position, AlwaysFalse{}); return MergeResult::Annihilated; - } else if (Variant::EqualTo{collate}(leV, rngL)) { + } else if (MergeEqualT{args...}(leV, rngL)) { target.SetCondAndValues(CondEq, std::move(le).Values()); target.Distinct(distinct); } else if (less(leV, rngR)) { @@ -1031,25 +1067,25 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRangeLe(Query target.Distinct(distinct); } else { range.Distinct(distinct); - if constexpr (needSwitch == NeedSwitch::Yes) { + if (needSwitch == NeedSwitch::Yes) { le = std::move(range); } } return MergeResult::Merged; } -template -QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRangeGe(QueryEntry &range, QueryEntry &ge, bool distinct, - size_t position, const CollateOpts &collate) { +template +QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRangeGe(NeedSwitch needSwitch, QueryEntry &range, QueryEntry &ge, + bool distinct, size_t position, const CmpArgs &...args) { const Variant &geV = ge.Values()[0]; const Variant &rngL = range.Values()[0]; const Variant &rngR = range.Values()[1]; - const Variant::Less less{collate}; - QueryEntry &target = needSwitch == NeedSwitch::No ? range : ge; + const MergeLessT less{args...}; + QueryEntry &target = needSwitch == NeedSwitch::Yes ? ge : range; if (less(rngR, geV)) { SetValue(position, AlwaysFalse{}); return MergeResult::Annihilated; - } else if (Variant::EqualTo{collate}(geV, rngR)) { + } else if (MergeEqualT{args...}(geV, rngR)) { target.SetCondAndValues(CondEq, std::move(ge).Values()); target.Distinct(distinct); } else if (less(rngL, geV)) { @@ -1057,22 +1093,23 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRangeGe(Query target.Distinct(distinct); } else { range.Distinct(distinct); - if constexpr (needSwitch == NeedSwitch::Yes) { + if (needSwitch == NeedSwitch::Yes) { ge = std::move(range); } } return MergeResult::Merged; } +template QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesRange(QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position, - const CollateOpts &collate) { - const Variant::Less less{collate}; + const CmpArgs &...args) { + const MergeLessT less{args...}; QueryEntry &left = less(lqe.Values()[0], rqe.Values()[0]) ? rqe : lqe; QueryEntry &right = less(rqe.Values()[1], lqe.Values()[1]) ? rqe : lqe; if (less(right.Values()[1], left.Values()[0])) { SetValue(position, AlwaysFalse{}); return MergeResult::Annihilated; - } else if (Variant::EqualTo{collate}(left.Values()[0], right.Values()[1])) { + } else if (MergeEqualT{args...}(left.Values()[0], right.Values()[1])) { lqe.SetCondAndValues(CondEq, VariantArray::Create(std::move(left).Values()[0])); lqe.Distinct(distinct); } else { @@ -1113,40 +1150,45 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntriesDWithin(Query } } +template QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntries(size_t lhs, size_t rhs, MergeOrdered mergeOrdered, - const CollateOpts &collate) { - QueryEntry &lqe = Get(lhs); - QueryEntry &rqe = Get(rhs); + const CmpArgs &...args) { + auto &lqe = Get(lhs); + auto &rqe = Get(rhs); const bool distinct = lqe.Distinct() || rqe.Distinct(); - const Variant::Less less{collate}; + const MergeLessT less{args...}; switch (lqe.Condition()) { case CondEq: case CondSet: switch (rqe.Condition()) { case CondEq: case CondSet: - return mergeQueryEntriesSetSet(lqe, rqe, distinct, lhs, collate); + return mergeQueryEntriesSetSet(lqe, rqe, distinct, lhs, args...); case CondAllSet: - return mergeQueryEntriesAllSetSet(rqe, lqe, distinct, lhs, collate); + return mergeQueryEntriesAllSetSet(NeedSwitch::Yes, rqe, lqe, distinct, lhs, args...); case CondLt: - return mergeQueryEntriesSetNotSet( - lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return !less(v, rv); }, distinct, lhs, mergeOrdered); + return mergeQueryEntriesSetNotSet( + NeedSwitch::No, lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return !less(v, rv); }, distinct, lhs, + mergeOrdered); case CondLe: - return mergeQueryEntriesSetNotSet( - lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return less(rv, v); }, distinct, lhs, mergeOrdered); + return mergeQueryEntriesSetNotSet( + NeedSwitch::No, lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return less(rv, v); }, distinct, lhs, + mergeOrdered); case CondGe: - return mergeQueryEntriesSetNotSet( - lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return less(v, rv); }, distinct, lhs, mergeOrdered); + return mergeQueryEntriesSetNotSet( + NeedSwitch::No, lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return less(v, rv); }, distinct, lhs, + mergeOrdered); case CondGt: - return mergeQueryEntriesSetNotSet( - lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return !less(rv, v); }, distinct, lhs, mergeOrdered); + return mergeQueryEntriesSetNotSet( + NeedSwitch::No, lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return !less(rv, v); }, distinct, lhs, + mergeOrdered); case CondRange: - return mergeQueryEntriesSetNotSet( - lqe, rqe, + return mergeQueryEntriesSetNotSet( + NeedSwitch::No, lqe, rqe, [&rv1 = rqe.Values()[0], &rv2 = rqe.Values()[1], &less](const Variant &v) { return less(v, rv1) || less(rv2, v); }, distinct, lhs, mergeOrdered); case CondAny: - return mergeQueryEntriesAny(rqe, lqe, distinct, lhs); + return mergeQueryEntriesAny(NeedSwitch::No, rqe, lqe, distinct, lhs); case CondEmpty: SetValue(lhs, AlwaysFalse{}); return MergeResult::Annihilated; @@ -1159,28 +1201,32 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntries(size_t lhs, switch (rqe.Condition()) { case CondEq: case CondSet: - return mergeQueryEntriesAllSetSet(lqe, rqe, distinct, lhs, collate); + return mergeQueryEntriesAllSetSet(NeedSwitch::No, lqe, rqe, distinct, lhs, args...); case CondAllSet: - return mergeQueryEntriesAllSetAllSet(lqe, rqe, distinct, lhs, collate); + return mergeQueryEntriesAllSetAllSet(lqe, rqe, distinct, lhs, args...); case CondLt: - return mergeQueryEntriesAllSetNotSet( - lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return !less(v, rv); }, distinct, lhs, collate); + return mergeQueryEntriesAllSetNotSet( + NeedSwitch::No, lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return !less(v, rv); }, distinct, lhs, + args...); case CondLe: - return mergeQueryEntriesAllSetNotSet( - lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return less(rv, v); }, distinct, lhs, collate); + return mergeQueryEntriesAllSetNotSet( + NeedSwitch::No, lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return less(rv, v); }, distinct, lhs, + args...); case CondGe: - return mergeQueryEntriesAllSetNotSet( - lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return less(v, rv); }, distinct, lhs, collate); + return mergeQueryEntriesAllSetNotSet( + NeedSwitch::No, lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return less(v, rv); }, distinct, lhs, + args...); case CondGt: - return mergeQueryEntriesAllSetNotSet( - lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return !less(rv, v); }, distinct, lhs, collate); + return mergeQueryEntriesAllSetNotSet( + NeedSwitch::No, lqe, rqe, [&rv = rqe.Values()[0], &less](const Variant &v) { return !less(rv, v); }, distinct, lhs, + args...); case CondRange: - return mergeQueryEntriesAllSetNotSet( - lqe, rqe, + return mergeQueryEntriesAllSetNotSet( + NeedSwitch::No, lqe, rqe, [&rv1 = rqe.Values()[0], &rv2 = rqe.Values()[1], &less](const Variant &v) { return less(v, rv1) || less(rv2, v); }, - distinct, lhs, collate); + distinct, lhs, args...); case CondAny: - return mergeQueryEntriesAny(rqe, lqe, distinct, lhs); + return mergeQueryEntriesAny(NeedSwitch::No, rqe, lqe, distinct, lhs); case CondEmpty: SetValue(lhs, AlwaysFalse{}); return MergeResult::Annihilated; @@ -1193,21 +1239,23 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntries(size_t lhs, switch (rqe.Condition()) { case CondEq: case CondSet: - return mergeQueryEntriesSetNotSet( - rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return !less(v, lv); }, distinct, lhs, mergeOrdered); + return mergeQueryEntriesSetNotSet( + NeedSwitch::Yes, rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return !less(v, lv); }, distinct, lhs, + mergeOrdered); case CondAllSet: - return mergeQueryEntriesAllSetNotSet( - rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return !less(v, lv); }, distinct, lhs, collate); + return mergeQueryEntriesAllSetNotSet( + NeedSwitch::Yes, rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return !less(v, lv); }, distinct, lhs, + args...); case CondAny: - return mergeQueryEntriesAny(rqe, lqe, distinct, lhs); + return mergeQueryEntriesAny(NeedSwitch::No, rqe, lqe, distinct, lhs); case CondLt: case CondLe: - return mergeQueryEntriesLt(lqe, rqe, distinct, collate); + return mergeQueryEntriesLt(lqe, rqe, distinct, args...); case CondGt: case CondGe: - return mergeQueryEntriesLtGt(lqe, rqe, lhs, collate); + return mergeQueryEntriesLtGt(lqe, rqe, lhs, args...); case CondRange: - return mergeQueryEntriesRangeLt(rqe, lqe, distinct, lhs, collate); + return mergeQueryEntriesRangeLt(NeedSwitch::Yes, rqe, lqe, distinct, lhs, args...); case CondEmpty: SetValue(lhs, AlwaysFalse{}); return MergeResult::Annihilated; @@ -1220,22 +1268,24 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntries(size_t lhs, switch (rqe.Condition()) { case CondEq: case CondSet: - return mergeQueryEntriesSetNotSet( - rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return less(lv, v); }, distinct, lhs, mergeOrdered); + return mergeQueryEntriesSetNotSet( + NeedSwitch::Yes, rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return less(lv, v); }, distinct, lhs, + mergeOrdered); case CondAllSet: - return mergeQueryEntriesAllSetNotSet( - rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return less(lv, v); }, distinct, lhs, collate); + return mergeQueryEntriesAllSetNotSet( + NeedSwitch::Yes, rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return less(lv, v); }, distinct, lhs, + args...); case CondLt: case CondLe: - return mergeQueryEntriesLt(lqe, rqe, distinct, collate); + return mergeQueryEntriesLt(lqe, rqe, distinct, args...); case CondGt: - return mergeQueryEntriesLtGt(lqe, rqe, lhs, collate); + return mergeQueryEntriesLtGt(lqe, rqe, lhs, args...); case CondGe: - return mergeQueryEntriesLeGe(lqe, rqe, distinct, lhs, collate); + return mergeQueryEntriesLeGe(NeedSwitch::No, lqe, rqe, distinct, lhs, args...); case CondRange: - return mergeQueryEntriesRangeLe(rqe, lqe, distinct, lhs, collate); + return mergeQueryEntriesRangeLe(NeedSwitch::Yes, rqe, lqe, distinct, lhs, args...); case CondAny: - return mergeQueryEntriesAny(rqe, lqe, distinct, lhs); + return mergeQueryEntriesAny(NeedSwitch::No, rqe, lqe, distinct, lhs); case CondEmpty: SetValue(lhs, AlwaysFalse{}); return MergeResult::Annihilated; @@ -1248,21 +1298,23 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntries(size_t lhs, switch (rqe.Condition()) { case CondEq: case CondSet: - return mergeQueryEntriesSetNotSet( - rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return !less(lv, v); }, distinct, lhs, mergeOrdered); + return mergeQueryEntriesSetNotSet( + NeedSwitch::Yes, rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return !less(lv, v); }, distinct, lhs, + mergeOrdered); case CondAllSet: - return mergeQueryEntriesAllSetNotSet( - rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return !less(lv, v); }, distinct, lhs, collate); + return mergeQueryEntriesAllSetNotSet( + NeedSwitch::Yes, rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return !less(lv, v); }, distinct, lhs, + args...); case CondGt: case CondGe: - return mergeQueryEntriesGt(lqe, rqe, distinct, collate); + return mergeQueryEntriesGt(lqe, rqe, distinct, args...); case CondLt: case CondLe: - return mergeQueryEntriesLtGt(rqe, lqe, lhs, collate); + return mergeQueryEntriesLtGt(rqe, lqe, lhs, args...); case CondRange: - return mergeQueryEntriesRangeGt(rqe, lqe, distinct, lhs, collate); + return mergeQueryEntriesRangeGt(NeedSwitch::Yes, rqe, lqe, distinct, lhs, args...); case CondAny: - return mergeQueryEntriesAny(rqe, lqe, distinct, lhs); + return mergeQueryEntriesAny(NeedSwitch::No, rqe, lqe, distinct, lhs); case CondEmpty: SetValue(lhs, AlwaysFalse{}); return MergeResult::Annihilated; @@ -1275,22 +1327,24 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntries(size_t lhs, switch (rqe.Condition()) { case CondEq: case CondSet: - return mergeQueryEntriesSetNotSet( - rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return less(v, lv); }, distinct, lhs, mergeOrdered); + return mergeQueryEntriesSetNotSet( + NeedSwitch::Yes, rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return less(v, lv); }, distinct, lhs, + mergeOrdered); case CondAllSet: - return mergeQueryEntriesAllSetNotSet( - rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return less(v, lv); }, distinct, lhs, collate); + return mergeQueryEntriesAllSetNotSet( + NeedSwitch::Yes, rqe, lqe, [&lv = lqe.Values()[0], &less](const Variant &v) { return less(v, lv); }, distinct, lhs, + args...); case CondGt: case CondGe: - return mergeQueryEntriesGt(lqe, rqe, distinct, collate); + return mergeQueryEntriesGt(lqe, rqe, distinct, args...); case CondLt: - return mergeQueryEntriesLtGt(rqe, lqe, lhs, collate); + return mergeQueryEntriesLtGt(rqe, lqe, lhs, args...); case CondLe: - return mergeQueryEntriesLeGe(rqe, lqe, distinct, lhs, collate); + return mergeQueryEntriesLeGe(NeedSwitch::Yes, rqe, lqe, distinct, lhs, args...); case CondRange: - return mergeQueryEntriesRangeGe(rqe, lqe, distinct, lhs, collate); + return mergeQueryEntriesRangeGe(NeedSwitch::Yes, rqe, lqe, distinct, lhs, args...); case CondAny: - return mergeQueryEntriesAny(rqe, lqe, distinct, lhs); + return mergeQueryEntriesAny(NeedSwitch::No, rqe, lqe, distinct, lhs); case CondEmpty: SetValue(lhs, AlwaysFalse{}); return MergeResult::Annihilated; @@ -1303,27 +1357,27 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntries(size_t lhs, switch (rqe.Condition()) { case CondEq: case CondSet: - return mergeQueryEntriesSetNotSet( - rqe, lqe, + return mergeQueryEntriesSetNotSet( + NeedSwitch::Yes, rqe, lqe, [&lv1 = lqe.Values()[0], &lv2 = lqe.Values()[1], &less](const Variant &v) { return less(v, lv1) || less(lv2, v); }, distinct, lhs, mergeOrdered); case CondAllSet: - return mergeQueryEntriesAllSetNotSet( - rqe, lqe, + return mergeQueryEntriesAllSetNotSet( + NeedSwitch::Yes, rqe, lqe, [&lv1 = lqe.Values()[0], &lv2 = lqe.Values()[1], &less](const Variant &v) { return less(v, lv1) || less(lv2, v); }, - distinct, lhs, collate); + distinct, lhs, args...); case CondLt: - return mergeQueryEntriesRangeLt(lqe, rqe, distinct, lhs, collate); + return mergeQueryEntriesRangeLt(NeedSwitch::No, lqe, rqe, distinct, lhs, args...); case CondLe: - return mergeQueryEntriesRangeLe(lqe, rqe, distinct, lhs, collate); + return mergeQueryEntriesRangeLe(NeedSwitch::No, lqe, rqe, distinct, lhs, args...); case CondGt: - return mergeQueryEntriesRangeGt(lqe, rqe, distinct, lhs, collate); + return mergeQueryEntriesRangeGt(NeedSwitch::No, lqe, rqe, distinct, lhs, args...); case CondGe: - return mergeQueryEntriesRangeGe(lqe, rqe, distinct, lhs, collate); + return mergeQueryEntriesRangeGe(NeedSwitch::No, lqe, rqe, distinct, lhs, args...); case CondRange: - return mergeQueryEntriesRange(lqe, rqe, distinct, lhs, collate); + return mergeQueryEntriesRange(lqe, rqe, distinct, lhs, args...); case CondAny: - return mergeQueryEntriesAny(rqe, lqe, distinct, lhs); + return mergeQueryEntriesAny(NeedSwitch::No, rqe, lqe, distinct, lhs); case CondEmpty: SetValue(lhs, AlwaysFalse{}); return MergeResult::Annihilated; @@ -1333,7 +1387,7 @@ QueryPreprocessor::MergeResult QueryPreprocessor::mergeQueryEntries(size_t lhs, } break; case CondAny: - return mergeQueryEntriesAny(lqe, rqe, distinct, lhs); + return mergeQueryEntriesAny(NeedSwitch::Yes, lqe, rqe, distinct, lhs); case CondEmpty: switch (rqe.Condition()) { case CondEq: @@ -1384,7 +1438,7 @@ void QueryPreprocessor::AddDistinctEntries(const h_vector &aggreg if (ag.Type() != AggDistinct) continue; assertrx_throw(ag.Names().size() == 1); Append(wasAdded ? OpOr : OpAnd, ag.Names()[0], QueryEntry::DistinctTag{}); - QueryEntry &qe = Get(LastAppendedElement()); + auto &qe = Get(LastAppendedElement()); SetQueryField(qe.FieldData(), ns_); checkStrictMode(qe.FieldData()); wasAdded = true; @@ -1396,15 +1450,15 @@ std::pair QueryPreprocessor::queryValuesFromOnCondition( JoinPreResult::CPtr joinPreresult, const QueryJoinEntry &joinEntry, CondType condition, int mainQueryMaxIterations, const RdxContext &rdxCtx) { - size_t limit; - const auto &rNsCfg = rightNs.Config(); + size_t limit = 0; + const auto &rNsCfg = rightNs.config(); if (rNsCfg.maxPreselectSize == 0) { - limit = std::max(rNsCfg.minPreselectSize, rightNs.ItemsCount() * rNsCfg.maxPreselectPart); + limit = std::max(rNsCfg.minPreselectSize, rightNs.itemsCount() * rNsCfg.maxPreselectPart); } else if (rNsCfg.maxPreselectPart == 0.0) { limit = rNsCfg.maxPreselectSize; } else { limit = - std::min(std::max(rNsCfg.minPreselectSize, rightNs.ItemsCount() * rNsCfg.maxPreselectPart), rNsCfg.maxPreselectSize); + std::min(std::max(rNsCfg.minPreselectSize, rightNs.itemsCount() * rNsCfg.maxPreselectPart), rNsCfg.maxPreselectSize); } joinQuery.Explain(query_.NeedExplain()); joinQuery.Limit(limit + 2); @@ -1434,7 +1488,7 @@ std::pair QueryPreprocessor::queryValuesFromOnCondition( case CondEmpty: case CondLike: case CondDWithin: - throw Error(errQueryExec, "Unsupported condition in ON statment: %s", CondTypeToStr(condition)); + throw Error(errQueryExec, "Unsupported condition in ON statement: %s", CondTypeToStr(condition)); } LocalQueryResults qr; @@ -1478,7 +1532,7 @@ std::pair QueryPreprocessor::queryValuesFromOnCondition( case CondLike: case CondDWithin: default: - throw Error(errQueryExec, "Unsupported condition in ON statment: %s", CondTypeToStr(condition)); + throw Error(errQueryExec, "Unsupported condition in ON statement: %s", CondTypeToStr(condition)); } } @@ -1493,7 +1547,7 @@ std::pair QueryPreprocessor::queryValuesFromOnCondition( case CondLe: case CondGt: case CondGe: { - const JoinPreResult::Values &values = std::get(joinedSelector.PreResult().preselectedPayload); + const JoinPreResult::Values &values = std::get(joinedSelector.PreResult().payload); VariantArray buffer, keyValues; for (const ItemRef &item : values) { assertrx_throw(!item.Value().IsFree()); @@ -1525,7 +1579,7 @@ std::pair QueryPreprocessor::queryValuesFromOnCondition( case CondLike: case CondDWithin: default: - throw Error(errQueryExec, "Unsupported condition in ON statment: %s", CondTypeToStr(condition)); + throw Error(errQueryExec, "Unsupported condition in ON statement: %s", CondTypeToStr(condition)); } } @@ -1536,7 +1590,7 @@ void QueryPreprocessor::briefDump(size_t from, size_t to, const std::vector if (it != from || container_[it].operation != OpAnd) { ser << container_[it].operation << ' '; } - container_[it].Visit([](const SubQueryEntry &) { assertrx_throw(0); }, [](const SubQueryFieldEntry &) { assertrx_throw(0); }, + container_[it].Visit([](const SubQueryEntry &) { throw_as_assert; }, [](const SubQueryFieldEntry &) { throw_as_assert; }, [&](const QueryEntriesBracket &b) { ser << "("; briefDump(it + 1, Next(it), joinedSelectors, ser); @@ -1562,7 +1616,7 @@ size_t QueryPreprocessor::injectConditionsFromJoins(const size_t from, size_t to size_t injectedCount = 0; for (size_t cur = from; cur < to; cur = Next(cur)) { container_[cur].Visit( - [](const SubQueryEntry &) { assertrx_throw(0); }, [](const SubQueryFieldEntry &) { assertrx_throw(0); }, + [](const SubQueryEntry &) { throw_as_assert; }, [](const SubQueryFieldEntry &) { throw_as_assert; }, Skip{}, [&](const QueryEntriesBracket &) { const size_t injCount = @@ -1578,20 +1632,20 @@ size_t QueryPreprocessor::injectConditionsFromJoins(const size_t from, size_t to JoinedSelector &joinedSelector = js[joinIndex]; const JoinPreResult &preResult = joinedSelector.PreResult(); assertrx_throw(joinedSelector.PreSelectMode() == JoinPreSelectMode::Execute); - const bool byValues = std::holds_alternative(preResult.preselectedPayload); + const bool byValues = std::holds_alternative(preResult.payload); auto explainJoinOn = ExplainPolicy::AppendJoinOnExplain(explainOnInjections); explainJoinOn.Init(jqe, js, byValues); // Checking if we are able to preselect something from RightNs, or there are preselected results if (!byValues) { - const auto &rNsCfg = joinedSelector.RightNs()->Config(); + const auto &rNsCfg = joinedSelector.RightNs()->config(); if (rNsCfg.maxPreselectSize == 0 && rNsCfg.maxPreselectPart == 0.0) { explainJoinOn.Skipped("maxPreselectSize and maxPreselectPart == 0"sv); return; } } else { - if (!std::get(preResult.preselectedPayload).IsPreselectAllowed()) { + if (!std::get(preResult.payload).IsPreselectAllowed()) { explainJoinOn.Skipped("Preselect is not allowed"sv); return; } @@ -1677,7 +1731,7 @@ size_t QueryPreprocessor::injectConditionsFromJoins(const size_t from, size_t to case CondEmpty: case CondLike: case CondDWithin: - throw Error(errQueryExec, "Unsupported condition in ON statment: %s", CondTypeToStr(condition)); + throw Error(errQueryExec, "Unsupported condition in ON statement: %s", CondTypeToStr(condition)); } operation = OpAnd; break; @@ -1746,7 +1800,7 @@ size_t QueryPreprocessor::injectConditionsFromJoins(const size_t from, size_t to std::string explainSelect; AggType selectAggType; std::tie(queryCondition, values) = - (!std::holds_alternative(preResult.preselectedPayload) + (!std::holds_alternative(preResult.payload) ? queryValuesFromOnCondition(explainSelect, selectAggType, *joinedSelector.RightNs(), Query{joinedSelector.RightNsName()}, joinedSelector.PreResultPtr(), joinEntry, condition, embracedMaxIterations, rdxCtx) @@ -1858,14 +1912,14 @@ class JoinOnExplainEnabled { ConditionInjection &explainEntry_; }; + JoinOnExplainEnabled(JoinOnInjection &joinOn) noexcept : explainJoinOn_(joinOn), startTime_(ExplainCalc::Clock::now()) {} + +public: JoinOnExplainEnabled(const JoinOnExplainEnabled &) = delete; JoinOnExplainEnabled(JoinOnExplainEnabled &&) = delete; JoinOnExplainEnabled &operator=(const JoinOnExplainEnabled &) = delete; JoinOnExplainEnabled &operator=(JoinOnExplainEnabled &&) = delete; - JoinOnExplainEnabled(JoinOnInjection &joinOn) noexcept : explainJoinOn_(joinOn), startTime_(ExplainCalc::Clock::now()) {} - -public: [[nodiscard]] static JoinOnExplainEnabled AppendJoinOnExplain(OnConditionInjections &explainOnInjections) { return {explainOnInjections.emplace_back()}; } @@ -1894,7 +1948,7 @@ class JoinOnExplainEnabled { void FailOnEntriesAsOrChain(size_t orChainLength) { using namespace std::string_view_literals; auto &conditions = explainJoinOn_.conditions; - assertrx(conditions.size() >= orChainLength); + assertrx_throw(conditions.size() >= orChainLength); // Marking On-injections as fail for removed entries. for (size_t jsz = conditions.size(), j = jsz - orChainLength; j < jsz; ++j) { conditions[j].succeed = false; @@ -1909,7 +1963,7 @@ class JoinOnExplainEnabled { void QueryPreprocessor::setQueryIndex(QueryField &qField, int idxNo, const NamespaceImpl &ns) { const auto &idx = *ns.indexes_[idxNo]; - std::vector compositeFieldsTypes; + QueryField::CompositeTypesVecT compositeFieldsTypes; if (idxNo >= ns.indexes_.firstCompositePos()) { #ifndef NDEBUG const bool ftIdx = IsFullText(idx.Type()); diff --git a/cpp_src/core/nsselecter/querypreprocessor.h b/cpp_src/core/nsselecter/querypreprocessor.h index 85028e866..d9c61b935 100644 --- a/cpp_src/core/nsselecter/querypreprocessor.h +++ b/cpp_src/core/nsselecter/querypreprocessor.h @@ -15,6 +15,8 @@ struct SelectCtxWithJoinPreSelect; class QueryPreprocessor : private QueryEntries { public: + enum class ValuesType : bool { Scalar, Composite }; + QueryPreprocessor(QueryEntries &&, NamespaceImpl *, const SelectCtx &); const QueryEntries &GetQueryEntries() const noexcept { return *this; } bool LookupQueryIndexes() { @@ -72,11 +74,11 @@ class QueryPreprocessor : private QueryEntries { bool IsFtExcluded() const noexcept { return ftEntry_.has_value(); } void ExcludeFtQuery(const RdxContext &); FtMergeStatuses &GetFtMergeStatuses() noexcept { - assertrx(ftPreselect_); + assertrx_throw(ftPreselect_); return *ftPreselect_; } FtPreselectT &&MoveFtPreselect() noexcept { - assertrx(ftPreselect_); + assertrx_throw(ftPreselect_); return std::move(*ftPreselect_); } bool IsFtPreselected() const noexcept { return ftPreselect_ && !ftEntry_; } @@ -84,7 +86,7 @@ class QueryPreprocessor : private QueryEntries { private: enum class NeedSwitch : bool { Yes = true, No = false }; - enum class MergeResult { NotMerged, Merged, Annihilated }; + enum class [[nodiscard]] MergeResult { NotMerged, Merged, Annihilated }; enum class MergeOrdered : bool { Yes = true, No = false }; struct FoundIndexInfo { enum class ConditionType { Incompatible = 0, Compatible = 1 }; @@ -102,42 +104,41 @@ class QueryPreprocessor : private QueryEntries { [[nodiscard]] bool forcedStage() const noexcept { return evaluationsCount_ == (desc_ ? 1 : 0); } [[nodiscard]] size_t lookupQueryIndexes(uint16_t dst, uint16_t srcBegin, uint16_t srcEnd); [[nodiscard]] size_t substituteCompositeIndexes(size_t from, size_t to); - [[nodiscard]] MergeResult mergeQueryEntries(size_t lhs, size_t rhs, MergeOrdered, const CollateOpts &); - [[nodiscard]] MergeResult mergeQueryEntriesSetSet(QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position, - const CollateOpts &); - template - [[nodiscard]] MergeResult mergeQueryEntriesAllSetSet(QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position, - const CollateOpts &); - [[nodiscard]] MergeResult mergeQueryEntriesAllSetAllSet(QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position, - const CollateOpts &); - template - [[nodiscard]] MergeResult mergeQueryEntriesAny(QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position); - template - [[nodiscard]] MergeResult mergeQueryEntriesSetNotSet(QueryEntry &lqe, QueryEntry &rqe, F filter, bool distinct, size_t position, - MergeOrdered); - template - [[nodiscard]] MergeResult mergeQueryEntriesAllSetNotSet(QueryEntry &lqe, QueryEntry &rqe, F filter, bool distinct, size_t position, - const CollateOpts &); - [[nodiscard]] MergeResult mergeQueryEntriesDWithin(QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position); - [[nodiscard]] MergeResult mergeQueryEntriesLt(QueryEntry &lqe, QueryEntry &rqe, bool distinct, const CollateOpts &); - [[nodiscard]] MergeResult mergeQueryEntriesGt(QueryEntry &lqe, QueryEntry &rqe, bool distinct, const CollateOpts &); - [[nodiscard]] MergeResult mergeQueryEntriesLtGt(QueryEntry &lqe, QueryEntry &rqe, size_t position, const CollateOpts &); - template - [[nodiscard]] MergeResult mergeQueryEntriesLeGe(QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position, const CollateOpts &); - template - [[nodiscard]] MergeResult mergeQueryEntriesRangeLt(QueryEntry &range, QueryEntry &ge, bool distinct, size_t position, - const CollateOpts &); - template - [[nodiscard]] MergeResult mergeQueryEntriesRangeLe(QueryEntry &range, QueryEntry &ge, bool distinct, size_t position, - const CollateOpts &); - template - [[nodiscard]] MergeResult mergeQueryEntriesRangeGt(QueryEntry &range, QueryEntry &ge, bool distinct, size_t position, - const CollateOpts &); - template - [[nodiscard]] MergeResult mergeQueryEntriesRangeGe(QueryEntry &range, QueryEntry &ge, bool distinct, size_t position, - const CollateOpts &); - [[nodiscard]] MergeResult mergeQueryEntriesRange(QueryEntry &range, QueryEntry &ge, bool distinct, size_t position, - const CollateOpts &); + template + MergeResult mergeQueryEntries(size_t lhs, size_t rhs, MergeOrdered, const CmpArgs &...); + template + MergeResult mergeQueryEntriesSetSet(QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position, const CmpArgs &...); + template + MergeResult mergeQueryEntriesAllSetSet(NeedSwitch, QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position, + const CmpArgs &...); + template + MergeResult mergeQueryEntriesAllSetAllSet(QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position, const CmpArgs &...); + MergeResult mergeQueryEntriesAny(NeedSwitch, QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position); + template + MergeResult mergeQueryEntriesSetNotSet(NeedSwitch, QueryEntry &lqe, QueryEntry &rqe, F filter, bool distinct, size_t position, + MergeOrdered); + template + MergeResult mergeQueryEntriesAllSetNotSet(NeedSwitch, QueryEntry &lqe, QueryEntry &rqe, F filter, bool distinct, size_t position, + const CmpArgs &...); + MergeResult mergeQueryEntriesDWithin(QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position); + template + MergeResult mergeQueryEntriesLt(QueryEntry &lqe, QueryEntry &rqe, bool distinct, const CmpArgs &...); + template + MergeResult mergeQueryEntriesGt(QueryEntry &lqe, QueryEntry &rqe, bool distinct, const CmpArgs &...); + template + MergeResult mergeQueryEntriesLtGt(QueryEntry &lqe, QueryEntry &rqe, size_t position, const CmpArgs &...); + template + MergeResult mergeQueryEntriesLeGe(NeedSwitch, QueryEntry &lqe, QueryEntry &rqe, bool distinct, size_t position, const CmpArgs &...); + template + MergeResult mergeQueryEntriesRangeLt(NeedSwitch, QueryEntry &range, QueryEntry &ge, bool distinct, size_t position, const CmpArgs &...); + template + MergeResult mergeQueryEntriesRangeLe(NeedSwitch, QueryEntry &range, QueryEntry &ge, bool distinct, size_t position, const CmpArgs &...); + template + MergeResult mergeQueryEntriesRangeGt(NeedSwitch, QueryEntry &range, QueryEntry &ge, bool distinct, size_t position, const CmpArgs &...); + template + MergeResult mergeQueryEntriesRangeGe(NeedSwitch, QueryEntry &range, QueryEntry &ge, bool distinct, size_t position, const CmpArgs &...); + template + MergeResult mergeQueryEntriesRange(QueryEntry &range, QueryEntry &ge, bool distinct, size_t position, const CmpArgs &...); [[nodiscard]] const std::vector *getCompositeIndex(int field) const noexcept; void initIndexedQueries(size_t begin, size_t end); [[nodiscard]] const Index *findMaxIndex(QueryEntries::const_iterator begin, QueryEntries::const_iterator end) const; diff --git a/cpp_src/core/nsselecter/selectiterator.cc b/cpp_src/core/nsselecter/selectiterator.cc index 0dc9dc5cd..816cbcf9b 100644 --- a/cpp_src/core/nsselecter/selectiterator.cc +++ b/cpp_src/core/nsselecter/selectiterator.cc @@ -1,4 +1,3 @@ - #include "selectiterator.h" #include diff --git a/cpp_src/core/nsselecter/selectiterator.h b/cpp_src/core/nsselecter/selectiterator.h index 7384cf389..25e2381ea 100644 --- a/cpp_src/core/nsselecter/selectiterator.h +++ b/cpp_src/core/nsselecter/selectiterator.h @@ -23,7 +23,6 @@ class SelectIterator : public SelectKeyResult { UnbuiltSortOrdersIndex, }; - SelectIterator() = default; SelectIterator(SelectKeyResult &&res, bool dist, std::string &&n, IteratorFieldKind fKind, bool forcedFirst = false) noexcept : SelectKeyResult(std::move(res)), distinct(dist), @@ -156,7 +155,7 @@ class SelectIterator : public SelectKeyResult { /// Current rowId index since the beginning /// of current SingleKeyValue object. int Pos() const noexcept { - assertrx(!lastIt_->useBtree_ && (type_ != UnbuiltSortOrdersIndex)); + assertrx_throw(!lastIt_->useBtree_ && (type_ != UnbuiltSortOrdersIndex)); return lastIt_->it_ - lastIt_->begin_ - 1; } @@ -172,7 +171,7 @@ class SelectIterator : public SelectKeyResult { fwdIter->ExcludeLastSet(); } } else if (!End() && lastIt_ != end() && lastVal_ == rowId) { - assertrx(!lastIt_->isRange_); + assertrx_throw(!lastIt_->isRange_); if (lastIt_->useBtree_) { lastIt_->itset_ = lastIt_->setend_; lastIt_->ritset_ = lastIt_->setrend_; @@ -233,7 +232,7 @@ class SelectIterator : public SelectKeyResult { bool distinct = false; std::string name; - IteratorFieldKind fieldKind; + IteratorFieldKind fieldKind = IteratorFieldKind::None; protected: // Iterates to a next item of result diff --git a/cpp_src/core/nsselecter/selectiteratorcontainer.cc b/cpp_src/core/nsselecter/selectiteratorcontainer.cc index dfced98a1..7ef62183e 100644 --- a/cpp_src/core/nsselecter/selectiteratorcontainer.cc +++ b/cpp_src/core/nsselecter/selectiteratorcontainer.cc @@ -26,7 +26,7 @@ void SelectIteratorContainer::SortByCost(int expectedIterations) { for (; positionOfTmp < indexes.size(); ++positionOfTmp) { if (indexes[positionOfTmp] == i) break; } - assertrx(positionOfTmp < indexes.size()); + assertrx_throw(positionOfTmp < indexes.size()); Container::value_type tmp = std::move(container_[i]); container_[i] = std::move(container_[indexes[i]]); container_[indexes[i]] = std::move(tmp); @@ -60,7 +60,34 @@ void SelectIteratorContainer::sortByCost(span indexes, span co costs[indexes[j]] = cst; } } - std::stable_sort(indexes.begin() + from, indexes.begin() + to, [&costs](unsigned i1, unsigned i2) { return costs[i1] < costs[i2]; }); + // GCC's std::stable_sort performs allocations even in the simpliest scenarios, so handling some of them explicitly + switch (to - from) { + case 0: + case 1: + break; + case 2: { + auto it = indexes.begin() + from; + auto &a = *(it++); + auto &b = *(it); + if (costs[a] > costs[b]) { + std::swap(a, b); + } + break; + } + case 3: { + auto it = indexes.begin() + from; + auto &a = *(it++); + auto &b = *(it++); + auto &c = *(it); + if (costs[a] > costs[b]) std::swap(a, b); + if (costs[b] > costs[c]) std::swap(b, c); + if (costs[a] > costs[b]) std::swap(a, b); + break; + } + default: + std::stable_sort(indexes.begin() + from, indexes.begin() + to, + [&costs](unsigned i1, unsigned i2) { return costs[i1] < costs[i2]; }); + } moveJoinsToTheBeginingOfORs(indexes, from, to); } @@ -182,13 +209,13 @@ void SelectIteratorContainer::CheckFirstQuery() { return; } } - assertrx(0); + throw_as_assert; } // Let iterators choose most effective algorithm void SelectIteratorContainer::SetExpectMaxIterations(int expectedIterations) { - assertrx(!Empty()); - assertrx(Is(0)); + assertrx_throw(!Empty()); + assertrx_throw(Is(0)); for (Container::iterator it = container_.begin() + 1; it != container_.end(); ++it) { if (it->Is()) { it->Value().SetExpectMaxIterations(expectedIterations); @@ -246,7 +273,7 @@ SelectKeyResults SelectIteratorContainer::processQueryEntry(const QueryEntry &qe isIndexSparse = index->Opts().IsSparse(); Index::SelectOpts opts; - opts.itemsCountInNamespace = ns.ItemsCount(); + opts.itemsCountInNamespace = ns.itemsCount(); if (!ns.SortOrdersBuilt()) opts.disableIdSetCache = 1; if (isQueryFt) { opts.forceComparator = 1; @@ -266,7 +293,7 @@ SelectKeyResults SelectIteratorContainer::processQueryEntry(const QueryEntry &qe opts.inTransaction = ctx_->inTransaction; auto ctx = selectFnc ? selectFnc->CreateCtx(qe.IndexNo()) : BaseFunctionCtx::Ptr{}; - if (ctx && ctx->type == BaseFunctionCtx::kFtCtx) ftCtx = reindexer::reinterpret_pointer_cast(ctx); + if (ctx && ctx->type == BaseFunctionCtx::kFtCtx) ftCtx = reindexer::static_ctx_pointer_cast(ctx); if (index->Opts().GetCollateMode() == CollateUTF8 || isIndexFt) { for (auto &key : qe.Values()) key.EnsureUTF8(); @@ -363,15 +390,9 @@ void SelectIteratorContainer::processQueryEntryResults(SelectKeyResults &&select void SelectIteratorContainer::processEqualPositions(const std::vector &equalPositions, const NamespaceImpl &ns, const QueryEntries &queries) { for (const auto &eqPos : equalPositions) { - const QueryEntry &firstQe{queries.Get(eqPos.queryEntriesPositions[0])}; - if (firstQe.Condition() == CondEmpty || (firstQe.Condition() == CondSet && firstQe.Values().empty())) { - throw Error(errLogic, "Condition IN(with empty parameter list), IS NULL, IS EMPTY not allowed for equal position!"); - } - EqualPositionComparator cmp{ns.payloadType_}; - - for (size_t i = 0; i < eqPos.queryEntriesPositions.size(); ++i) { - const QueryEntry &qe = queries.Get(eqPos.queryEntriesPositions[i]); + for (size_t i = 0, s = eqPos.size(); i < s; ++i) { + const QueryEntry &qe = queries.Get(eqPos[i]); if (qe.Condition() == CondEmpty || (qe.Condition() == CondSet && qe.Values().empty())) { throw Error(errLogic, "Condition IN(with empty parameter list), IS NULL, IS EMPTY not allowed for equal position!"); } @@ -384,8 +405,7 @@ void SelectIteratorContainer::processEqualPositions(const std::vectorOpts().collateOpts_); } } - - InsertAfter(eqPos.positionToInsertIterator, OpAnd, std::move(cmp)); + Append(OpAnd, std::move(cmp)); } } @@ -411,12 +431,12 @@ std::vector SelectIteratorContainer::pr throw Error(errParams, "equal positions fields should be unique: [%s]", getEpFieldsStr()); } std::unordered_set foundFields; - result[i].queryEntriesPositions.reserve(eqPos[i].size()); + result[i].reserve(eqPos[i].size()); for (size_t j = begin, next; j < end; j = next) { next = queries.Next(j); queries.Visit( - j, Skip{}, [](const SubQueryEntry &) { assertrx_throw(0); }, - [](const SubQueryFieldEntry &) { assertrx_throw(0); }, + j, Skip{}, [](const SubQueryEntry &) { throw_as_assert; }, + [](const SubQueryFieldEntry &) { throw_as_assert; }, [&](const QueryEntry &eq) { if (foundFields.find(eq.FieldName()) != foundFields.end()) { throw Error(errParams, "Equal position field '%s' found twice in enclosing bracket; equal position fields: [%s]", @@ -430,7 +450,7 @@ std::vector SelectIteratorContainer::pr "equal position fields: [%s]", eq.FieldName(), getEpFieldsStr()); } - result[i].queryEntriesPositions.push_back(j); + result[i].push_back(j); foundFields.insert(epFields.extract(it)); }, [&](const BetweenFieldsQueryEntry &eq) { // TODO equal positions for BetweenFieldsQueryEntry #1092 @@ -472,99 +492,80 @@ bool SelectIteratorContainer::prepareIteratorsForSelectLoop(QueryPreprocessor &q for (size_t i = begin, next = begin; i != end; i = next) { next = queries.Next(i); const OpType op = queries.GetOperation(i); - containFT = queries.Visit( - i, - [](const SubQueryEntry &) -> bool { - assertrx_throw(0); - abort(); - }, - [](const SubQueryFieldEntry &) -> bool { - assertrx_throw(0); - abort(); - }, - [&](const QueryEntriesBracket &) { - OpenBracket(op); - const bool contFT = - prepareIteratorsForSelectLoop(qPreproc, i + 1, next, sortId, isQueryFt, ns, selectFnc, ftCtx, rdxCtx); - if (contFT && (op == OpOr || (next < end && queries.GetOperation(next) == OpOr))) { - throw Error(errLogic, "OR operation is not allowed with bracket containing fulltext index"); - } - CloseBracket(); - return contFT; - }, - [&](const QueryEntry &qe) { - const bool isFT = qe.IsFieldIndexed() && IsFullText(ns.indexes_[qe.IndexNo()]->Type()); - if (isFT && (op == OpOr || (next < end && queries.GetOperation(next) == OpOr))) { - throw Error(errLogic, "OR operation is not allowed with fulltext index"); - } - SelectKeyResults selectResults; - - bool isIndexFt = false, isIndexSparse = false; - if (qe.IsFieldIndexed()) { - bool enableSortIndexOptimize = (ctx_->sortingContext.uncommitedIndex == qe.IndexNo()) && !sortIndexFound && - (op == OpAnd) && !qe.Distinct() && (begin == 0) && - (next == end || queries.GetOperation(next) != OpOr); - if (enableSortIndexOptimize) { - if (!IsExpectingOrderedResults(qe)) { - // Disable sorting index optimization if it somehow has incompatible conditions - enableSortIndexOptimize = false; - } - sortIndexFound = true; - } - selectResults = processQueryEntry(qe, enableSortIndexOptimize, ns, sortId, isQueryFt, selectFnc, isIndexFt, - isIndexSparse, ftCtx, qPreproc, rdxCtx); - } else { - auto strictMode = ns.config_.strictMode; - if (ctx_) { - if (ctx_->inTransaction) { - strictMode = StrictModeNone; - } else if (ctx_->query.GetStrictMode() != StrictModeNotSet) { - strictMode = ctx_->query.GetStrictMode(); - } - } - selectResults = processQueryEntry(qe, ns, strictMode); - } - std::optional nextOp; - if (next != end) { - nextOp = queries.GetOperation(next); + containFT = + queries.Visit( + i, [](const SubQueryEntry &) -> bool { throw_as_assert; }, [](const SubQueryFieldEntry &) -> bool { throw_as_assert; }, + [&](const QueryEntriesBracket &) { + OpenBracket(op); + const bool contFT = + prepareIteratorsForSelectLoop(qPreproc, i + 1, next, sortId, isQueryFt, ns, selectFnc, ftCtx, rdxCtx); + if (contFT && (op == OpOr || (next < end && queries.GetOperation(next) == OpOr))) { + throw Error(errLogic, "OR operation is not allowed with bracket containing fulltext index"); + } + CloseBracket(); + return contFT; + }, + [&](const QueryEntry &qe) { + const bool isFT = qe.IsFieldIndexed() && IsFullText(ns.indexes_[qe.IndexNo()]->Type()); + if (isFT && (op == OpOr || (next < end && queries.GetOperation(next) == OpOr))) { + throw Error(errLogic, "OR operation is not allowed with fulltext index"); + } + SelectKeyResults selectResults; + + bool isIndexFt = false, isIndexSparse = false; + if (qe.IsFieldIndexed()) { + bool enableSortIndexOptimize = (ctx_->sortingContext.uncommitedIndex == qe.IndexNo()) && !sortIndexFound && + (op == OpAnd) && !qe.Distinct() && (begin == 0) && + (next == end || queries.GetOperation(next) != OpOr); + if (enableSortIndexOptimize) { + if (!IsExpectingOrderedResults(qe)) { + // Disable sorting index optimization if it somehow has incompatible conditions + enableSortIndexOptimize = false; } - processQueryEntryResults(std::move(selectResults), op, ns, qe, isIndexFt, isIndexSparse, nextOp); - if (op != OpOr) { - for (auto &ep : equalPositions) { - const auto lastPosition = ep.queryEntriesPositions.back(); - if (i == lastPosition || (i > lastPosition && !ep.foundOr)) { - ep.positionToInsertIterator = Size() - 1; - } - } - } else { - for (auto &ep : equalPositions) { - if (i > ep.queryEntriesPositions.back()) ep.foundOr = true; - } + sortIndexFound = true; + } + selectResults = processQueryEntry(qe, enableSortIndexOptimize, ns, sortId, isQueryFt, selectFnc, isIndexFt, + isIndexSparse, ftCtx, qPreproc, rdxCtx); + } else { + auto strictMode = ns.config_.strictMode; + if (ctx_) { + if (ctx_->inTransaction) { + strictMode = StrictModeNone; + } else if (ctx_->query.GetStrictMode() != StrictModeNotSet) { + strictMode = ctx_->query.GetStrictMode(); } - return isFT; - }, - [this, op](const JoinQueryEntry &jqe) { - processJoinEntry(jqe, op); - return false; - }, - [&](const BetweenFieldsQueryEntry &qe) { - FieldsComparator fc{qe.LeftFieldName(), qe.Condition(), qe.RightFieldName(), ns.payloadType_}; - processField(fc, qe.LeftFieldData(), ns); - processField(fc, qe.RightFieldData(), ns); - Append(op, std::move(fc)); - return false; - }, - [this, op](const AlwaysFalse &) { - SelectKeyResult zeroScan; - zeroScan.emplace_back(0, 0); - Append(op, SelectIterator{std::move(zeroScan), false, "always_false", IteratorFieldKind::None, true}); - return false; - }, - [this, op](const AlwaysTrue &) { - Append(op, AlwaysTrue{}); - return false; - }) || - containFT; + } + selectResults = processQueryEntry(qe, ns, strictMode); + } + std::optional nextOp; + if (next != end) { + nextOp = queries.GetOperation(next); + } + processQueryEntryResults(std::move(selectResults), op, ns, qe, isIndexFt, isIndexSparse, nextOp); + return isFT; + }, + [this, op](const JoinQueryEntry &jqe) { + processJoinEntry(jqe, op); + return false; + }, + [&](const BetweenFieldsQueryEntry &qe) { + FieldsComparator fc{qe.LeftFieldName(), qe.Condition(), qe.RightFieldName(), ns.payloadType_}; + processField(fc, qe.LeftFieldData(), ns); + processField(fc, qe.RightFieldData(), ns); + Append(op, std::move(fc)); + return false; + }, + [this, op](const AlwaysFalse &) { + SelectKeyResult zeroScan; + zeroScan.emplace_back(0, 0); + Append(op, SelectIterator{std::move(zeroScan), false, "always_false", IteratorFieldKind::None, true}); + return false; + }, + [this, op](const AlwaysTrue &) { + Append(op, AlwaysTrue{}); + return false; + }) || + containFT; } processEqualPositions(equalPositions, ns, queries); return containFT; @@ -592,7 +593,7 @@ RX_ALWAYS_INLINE bool SelectIteratorContainer::checkIfSatisfyCondition(SelectIte RX_ALWAYS_INLINE bool SelectIteratorContainer::checkIfSatisfyCondition(JoinSelectIterator &it, PayloadValue &pv, IdType properRowId, bool match) { - assertrx(ctx_->joinedSelectors); + assertrx_throw(ctx_->joinedSelectors); ConstPayload pl(*pt_, pv); auto &joinedSelector = (*ctx_->joinedSelectors)[it.joinIndex]; return joinedSelector.Process(properRowId, ctx_->nsid, pl, match); @@ -770,7 +771,7 @@ void JoinSelectIterator::Dump(WrSerializer &ser, const std::vector, ComparatorIndexed, ComparatorIndexed, - ComparatorIndexed, ComparatorIndexed, ComparatorIndexed, - ComparatorIndexed, ComparatorIndexed, EqualPositionComparator, ComparatorNotIndexed> { - using Base = ExpressionTree, ComparatorIndexed, ComparatorIndexed, - ComparatorIndexed, ComparatorIndexed, ComparatorIndexed, - ComparatorIndexed, ComparatorIndexed, EqualPositionComparator, ComparatorNotIndexed>; + : public ExpressionTree, ComparatorIndexed, ComparatorIndexed, ComparatorIndexed, + ComparatorIndexed, ComparatorIndexed, ComparatorIndexed, + ComparatorIndexed, EqualPositionComparator, ComparatorNotIndexed> { + using Base = ExpressionTree, ComparatorIndexed, ComparatorIndexed, ComparatorIndexed, + ComparatorIndexed, ComparatorIndexed, ComparatorIndexed, + ComparatorIndexed, EqualPositionComparator, ComparatorNotIndexed>; public: SelectIteratorContainer(PayloadType pt = PayloadType(), SelectCtx *ctx = nullptr) @@ -58,11 +58,11 @@ class SelectIteratorContainer bool Process(PayloadValue &, bool *finish, IdType *rowId, IdType, bool match); bool IsSelectIterator(size_t i) const noexcept { - assertrx(i < Size()); + assertrx_throw(i < Size()); return container_[i].Is(); } bool IsJoinIterator(size_t i) const noexcept { - assertrx(i < container_.size()); + assertrx_throw(i < container_.size()); return container_[i].Is(); } bool IsDistinct(size_t i) const noexcept { @@ -94,7 +94,7 @@ class SelectIteratorContainer private: bool prepareIteratorsForSelectLoop(QueryPreprocessor &, size_t begin, size_t end, unsigned sortId, bool isFt, const NamespaceImpl &, SelectFunction::Ptr &, FtCtx::Ptr &, const RdxContext &); - void sortByCost(span indexes, span costs, unsigned from, unsigned to, int expectedIterations); + void sortByCost(span indexes, span costs, unsigned from, unsigned to, int expectedIterations); double fullCost(span indexes, unsigned i, unsigned from, unsigned to, int expectedIterations) const noexcept; double cost(span indexes, unsigned cur, int expectedIterations) const noexcept; double cost(span indexes, unsigned from, unsigned to, int expectedIterations) const noexcept; @@ -124,11 +124,7 @@ class SelectIteratorContainer void processJoinEntry(const JoinQueryEntry &, OpType); void processQueryEntryResults(SelectKeyResults &&, OpType, const NamespaceImpl &, const QueryEntry &, bool isIndexFt, bool isIndexSparse, std::optional nextOp); - struct EqualPositions { - h_vector queryEntriesPositions; - size_t positionToInsertIterator = 0; - bool foundOr = false; - }; + using EqualPositions = h_vector; void processEqualPositions(const std::vector &equalPositions, const NamespaceImpl &ns, const QueryEntries &queries); static std::vector prepareEqualPositions(const QueryEntries &queries, size_t begin, size_t end); diff --git a/cpp_src/core/nsselecter/sortingcontext.h b/cpp_src/core/nsselecter/sortingcontext.h index beaad3292..21dbb34b1 100644 --- a/cpp_src/core/nsselecter/sortingcontext.h +++ b/cpp_src/core/nsselecter/sortingcontext.h @@ -75,7 +75,7 @@ struct SortingContext { return false; } [[nodiscard]] const Entry &getFirstColumnEntry() const noexcept { - assertrx(!entries.empty()); + assertrx_throw(!entries.empty()); return entries[0]; } void resetOptimization() noexcept { diff --git a/cpp_src/core/nsselecter/substitutionhelpers.h b/cpp_src/core/nsselecter/substitutionhelpers.h index f53fee54e..8ebc54929 100644 --- a/cpp_src/core/nsselecter/substitutionhelpers.h +++ b/cpp_src/core/nsselecter/substitutionhelpers.h @@ -172,7 +172,7 @@ class EntriesRanges : h_vector { Base::const_reverse_iterator rbegin() const noexcept { return Base::rbegin(); } Base::const_reverse_iterator rend() const noexcept { return Base::rend(); } - void Add(span entries) { + void Add(span entries) { for (auto entry : entries) { auto insertionPos = Base::end(); bool wasMerged = false; diff --git a/cpp_src/core/parallelexecutor.cc b/cpp_src/core/parallelexecutor.cc index 33debc72a..f686b5485 100644 --- a/cpp_src/core/parallelexecutor.cc +++ b/cpp_src/core/parallelexecutor.cc @@ -3,7 +3,7 @@ namespace reindexer { -Error ParallelExecutor::createIntegralError(h_vector, 8> &errors, size_t clientCount) { +Error ParallelExecutor::createIntegralError(std::vector> &errors, size_t clientCount) { if (errors.empty()) { return {}; } @@ -37,9 +37,9 @@ Error ParallelExecutor::ExecSelect(const Query &query, QueryResults &result, con std::mutex mtx; size_t clientCompl = 0; - std::string errStringCompl; - h_vector, 8> clientErrors; + std::vector> clientErrors; + clientErrors.reserve(connections.size()); bool isLocalCall = false; std::deque> clientResults; @@ -107,7 +107,7 @@ Error ParallelExecutor::ExecSelect(const Query &query, QueryResults &result, con return createIntegralError(clientErrors, isLocalCall ? clientCount + 1 : clientCount); } -void ParallelExecutor::completionFunction(size_t clientCount, size_t &clientCompl, h_vector, 8> &clientErrors, +void ParallelExecutor::completionFunction(size_t clientCount, size_t &clientCompl, std::vector> &clientErrors, int shardId, std::mutex &mtx, std::condition_variable &cv, const Error &err) { std::lock_guard lck(mtx); clientCompl++; @@ -117,7 +117,7 @@ void ParallelExecutor::completionFunction(size_t clientCount, size_t &clientComp if (clientCompl == clientCount) { cv.notify_one(); } -}; +} size_t ParallelExecutor::countClientConnection(const sharding::ConnectionsVector &connections) { size_t count = 0; diff --git a/cpp_src/core/parallelexecutor.h b/cpp_src/core/parallelexecutor.h index 50f577ec0..094748770 100644 --- a/cpp_src/core/parallelexecutor.h +++ b/cpp_src/core/parallelexecutor.h @@ -29,9 +29,11 @@ class ParallelExecutor { std::mutex mtx; size_t clientCompl = 0; - h_vector, 8> clientErrors; + std::vector> clientErrors; + clientErrors.reserve(connections->size()); int isLocalCall = 0; - h_vector results; + std::vector results; + results.reserve(connections->size()); auto ward = rdxCtx.BeforeShardingProxy(); @@ -80,9 +82,9 @@ class ParallelExecutor { std::vector &result, const Predicate &predicated, std::string_view nsName, Args &&...args) { std::condition_variable cv; std::mutex mtx; - h_vector, 8> clientErrors; + std::vector> clientErrors; + clientErrors.reserve(connections->size()); size_t clientCompl = 0; - std::string errString; std::deque>> results; int isLocalCall = 0; auto ward = rdxCtx.BeforeShardingProxy(); @@ -133,8 +135,8 @@ class ParallelExecutor { std::function &&localAction); private: - Error createIntegralError(h_vector, 8> &errors, size_t clientCount); - void completionFunction(size_t clientCount, size_t &clientCompl, h_vector, 8> &clientErrors, int shardId, + Error createIntegralError(std::vector> &errors, size_t clientCount); + void completionFunction(size_t clientCount, size_t &clientCompl, std::vector> &clientErrors, int shardId, std::mutex &mtx, std::condition_variable &cv, const Error &err); size_t countClientConnection(const sharding::ConnectionsVector &connections); diff --git a/cpp_src/core/payload/payloadiface.cc b/cpp_src/core/payload/payloadiface.cc index 309f7b0dd..510eb8e54 100644 --- a/cpp_src/core/payload/payloadiface.cc +++ b/cpp_src/core/payload/payloadiface.cc @@ -88,7 +88,7 @@ void PayloadIface::GetByJsonPath(std::string_view jsonPath, TagsMatcher &tags return; } if (t_.Field(fieldIdx).IsArray()) { - IndexedTagsPath tagsPath = tagsMatcher.path2indexedtag(jsonPath, nullptr, false); + IndexedTagsPath tagsPath = tagsMatcher.path2indexedtag(jsonPath, false); if (tagsPath.back().IsWithIndex()) { kvs.clear(); kvs.emplace_back(Get(fieldIdx, tagsPath.back().Index())); @@ -97,7 +97,7 @@ void PayloadIface::GetByJsonPath(std::string_view jsonPath, TagsMatcher &tags } return Get(fieldIdx, kvs); } - GetByJsonPath(tagsMatcher.path2indexedtag(jsonPath, nullptr, false), kvs, expectedType); + GetByJsonPath(tagsMatcher.path2indexedtag(jsonPath, false), kvs, expectedType); } template @@ -126,7 +126,7 @@ void PayloadIface::GetByJsonPath(const IndexedTagsPath &tagsPath, VariantArra template void PayloadIface::GetByFieldsSet(const FieldsSet &fields, VariantArray &kvs, KeyValueType expectedType, - const std::vector &expectedCompositeTypes) const { + const h_vector &expectedCompositeTypes) const { if (expectedType.Is()) { kvs.Clear(); kvs.emplace_back(GetComposite(fields, expectedCompositeTypes)); @@ -146,7 +146,7 @@ void PayloadIface::GetByFieldsSet(const FieldsSet &fields, VariantArray &kvs, } template -Variant PayloadIface::GetComposite(const FieldsSet &fields, const std::vector &expectedTypes) const { +Variant PayloadIface::GetComposite(const FieldsSet &fields, const h_vector &expectedTypes) const { thread_local VariantArray buffer; buffer.clear(); assertrx_throw(fields.size() == expectedTypes.size()); diff --git a/cpp_src/core/payload/payloadiface.h b/cpp_src/core/payload/payloadiface.h index 0414f5b5a..47e73a240 100644 --- a/cpp_src/core/payload/payloadiface.h +++ b/cpp_src/core/payload/payloadiface.h @@ -35,11 +35,11 @@ class PayloadIface { // Get array as span of typed elements template - span GetArray(int field) & { + span GetArray(int field) const & { assertrx(field < Type().NumFields()); assertrx(Type().Field(field).IsArray()); auto *arr = reinterpret_cast(Field(field).p_); - return span(reinterpret_cast(v_->Ptr() + arr->offset), arr->len); + return span(reinterpret_cast(v_->Ptr() + arr->offset), arr->len); } // Get array len int GetArrayLen(int field) const { @@ -116,8 +116,8 @@ class PayloadIface { void GetByJsonPath(const TagsPath &jsonPath, VariantArray &, KeyValueType expectedType) const; void GetByJsonPath(const IndexedTagsPath &jsonPath, VariantArray &, KeyValueType expectedType) const; void GetByFieldsSet(const FieldsSet &, VariantArray &, KeyValueType expectedType, - const std::vector &expectedCompositeTypes) const; - [[nodiscard]] Variant GetComposite(const FieldsSet &, const std::vector &expectedTypes) const; + const h_vector &expectedCompositeTypes) const; + [[nodiscard]] Variant GetComposite(const FieldsSet &, const h_vector &expectedTypes) const; VariantArray GetIndexedArrayData(const IndexedTagsPath &jsonPath, int field, int &offset, int &size) const; // Get fields count diff --git a/cpp_src/core/payload/payloadtype.cc b/cpp_src/core/payload/payloadtype.cc index 2b9fec418..a22061287 100644 --- a/cpp_src/core/payload/payloadtype.cc +++ b/cpp_src/core/payload/payloadtype.cc @@ -1,7 +1,6 @@ #include "payloadtype.h" #include #include "core/keyvalue/key_string.h" -#include "core/keyvalue/variant.h" #include "payloadtypeimpl.h" #include "tools/serializer.h" @@ -61,6 +60,8 @@ void PayloadTypeImpl::Add(PayloadFieldType f) { throw Error(errLogic, "Cannot add field with name '%s' to namespace '%s'. Json path '%s' already used in field '%s'", f.Name(), Name(), jp, Field(res.first->second).Name()); } + + checkNewJsonPathBeforeAdd(f, jp); } fieldsByName_.emplace(f.Name(), int(fields_.size())); if (f.Type().Is()) { @@ -71,10 +72,10 @@ void PayloadTypeImpl::Add(PayloadFieldType f) { } bool PayloadTypeImpl::Drop(std::string_view field) { - auto it = fieldsByName_.find(field); - if (it == fieldsByName_.end()) return false; + auto itField = fieldsByName_.find(field); + if (itField == fieldsByName_.end()) return false; - int fieldIdx = it->second; + const auto fieldIdx = itField->second; for (auto &f : fieldsByName_) { if (f.second > fieldIdx) --f.second; } @@ -155,16 +156,16 @@ void PayloadTypeImpl::deserialize(Serializer &ser) { ser.GetVarUint(); - unsigned count = ser.GetVarUint(); + uint64_t count = ser.GetVarUint(); - for (unsigned i = 0; i < count; i++) { + for (uint64_t i = 0; i < count; i++) { const auto t = ser.GetKeyValueType(); std::string name(ser.GetVString()); std::vector jsonPaths; - int offset = ser.GetVarUint(); - [[maybe_unused]] const int elemSizeof = ser.GetVarUint(); + uint64_t offset = ser.GetVarUint(); + [[maybe_unused]] const uint64_t elemSizeof = ser.GetVarUint(); bool isArray = ser.GetVarUint(); - unsigned jsonPathsCount = ser.GetVarUint(); + uint64_t jsonPathsCount = ser.GetVarUint(); while (jsonPathsCount--) jsonPaths.emplace_back(ser.GetVString()); @@ -185,8 +186,8 @@ PayloadType::PayloadType(const PayloadTypeImpl &impl) PayloadType::~PayloadType() = default; const PayloadFieldType &PayloadType::Field(int field) const { return get()->Field(field); } const std::string &PayloadType::Name() const { return get()->Name(); } -void PayloadType::SetName(const std::string &name) { clone()->SetName(name); } -int PayloadType::NumFields() const { return get()->NumFields(); } +void PayloadType::SetName(std::string_view name) { clone()->SetName(name); } +int PayloadType::NumFields() const noexcept { return get()->NumFields(); } void PayloadType::Add(PayloadFieldType f) { clone()->Add(std::move(f)); } bool PayloadType::Drop(std::string_view field) { return clone()->Drop(field); } int PayloadType::FieldByName(std::string_view field) const { return get()->FieldByName(field); } @@ -205,4 +206,21 @@ void PayloadType::Dump(std::ostream &os, std::string_view step, std::string_view os << '\n' << offset << '}'; } +void PayloadTypeImpl::checkNewJsonPathBeforeAdd(const PayloadFieldType &f, const std::string &jsonPath) const { + const auto pos = jsonPath.find('.'); + if (pos < jsonPath.length() - 1) { + for (auto &fld : fields_) { + for (auto &jpfld : fld.JsonPaths()) { + // new field total overwrites existing one + if ((jsonPath.rfind(jpfld, 0) == 0) && (jsonPath[jpfld.length()] == '.')) { + throw Error(errLogic, + "Cannot add field with name '%s' (jsonpath '%s') and type '%s' to namespace '%s'." + " Already exists json path '%s' with type '%s' in field '%s'. Rewriting is impossible", + f.Name(), jsonPath, f.Type().Name(), Name(), jpfld, fld.Type().Name(), fld.Name()); + } + } + } + } +} + } // namespace reindexer diff --git a/cpp_src/core/payload/payloadtype.h b/cpp_src/core/payload/payloadtype.h index e0940e6df..e7d75388a 100644 --- a/cpp_src/core/payload/payloadtype.h +++ b/cpp_src/core/payload/payloadtype.h @@ -17,14 +17,14 @@ class PayloadType : public shared_cow_ptr { PayloadType(const PayloadType &) = default; PayloadType &operator=(PayloadType &&) noexcept = default; PayloadType &operator=(const PayloadType &) = default; - PayloadType(const std::string &name, std::initializer_list fields = {}); + explicit PayloadType(const std::string &name, std::initializer_list fields = {}); explicit PayloadType(const PayloadTypeImpl &impl); ~PayloadType(); const PayloadFieldType &Field(int field) const; const std::string &Name() const; - void SetName(const std::string &name); - int NumFields() const; + void SetName(std::string_view name); + int NumFields() const noexcept; void Add(PayloadFieldType); bool Drop(std::string_view field); int FieldByName(std::string_view field) const; diff --git a/cpp_src/core/payload/payloadtypeimpl.h b/cpp_src/core/payload/payloadtypeimpl.h index 882a66deb..be464bd28 100644 --- a/cpp_src/core/payload/payloadtypeimpl.h +++ b/cpp_src/core/payload/payloadtypeimpl.h @@ -20,15 +20,15 @@ class PayloadTypeImpl { public: PayloadTypeImpl(std::string name, std::initializer_list fields = {}) : fields_(fields), name_(std::move(name)) {} - const PayloadFieldType &Field(int field) const &noexcept { + const PayloadFieldType &Field(int field) const & noexcept { assertf(field < NumFields(), "%s: %d, %d", name_, field, NumFields()); return fields_[field]; } const PayloadFieldType &Field(int) const && = delete; - const std::string &Name() const &noexcept { return name_; } + const std::string &Name() const & noexcept { return name_; } const std::string &Name() const && = delete; - void SetName(std::string name) noexcept { name_ = std::move(name); } + void SetName(std::string_view name) noexcept { name_ = std::string(name); } int NumFields() const noexcept { return fields_.size(); } void Add(PayloadFieldType f); bool Drop(std::string_view field); @@ -36,7 +36,7 @@ class PayloadTypeImpl { bool FieldByName(std::string_view name, int &field) const noexcept; bool Contains(std::string_view field) const noexcept { return fieldsByName_.find(field) != fieldsByName_.end(); } int FieldByJsonPath(std::string_view jsonPath) const noexcept; - const std::vector &StrFields() const &noexcept { return strFields_; } + const std::vector &StrFields() const & noexcept { return strFields_; } const std::vector &StrFields() const && = delete; void serialize(WrSerializer &ser) const; @@ -47,6 +47,8 @@ class PayloadTypeImpl { void Dump(std::ostream &, std::string_view step, std::string_view offset) const; private: + void checkNewJsonPathBeforeAdd(const PayloadFieldType &f, const std::string &jsonPath) const; + std::vector fields_; FieldMap fieldsByName_; JsonPathMap fieldsByJsonPath_; diff --git a/cpp_src/core/query/dsl/dslencoder.cc b/cpp_src/core/query/dsl/dslencoder.cc index 574a38824..8aea893bf 100644 --- a/cpp_src/core/query/dsl/dslencoder.cc +++ b/cpp_src/core/query/dsl/dslencoder.cc @@ -1,44 +1,47 @@ #include "dslencoder.h" -#include #include "core/cjson/jsonbuilder.h" #include "core/keyvalue/p_string.h" #include "core/query/query.h" #include "core/queryresults/aggregationresult.h" #include "dslparser.h" #include "tools/logger.h" - -struct EnumClassHash { - template - size_t operator()(T t) const { - return static_cast(t); - } -}; +#include "vendor/frozen/unordered_map.h" namespace reindexer { namespace dsl { -static const std::unordered_map join_types = { - {InnerJoin, "inner"}, {LeftJoin, "left"}, {OrInnerJoin, "orinner"}}; - -static const std::unordered_map cond_map = { - {CondAny, "any"}, {CondEq, "eq"}, {CondLt, "lt"}, {CondLe, "le"}, {CondGt, "gt"}, {CondGe, "ge"}, - {CondRange, "range"}, {CondSet, "set"}, {CondAllSet, "allset"}, {CondEmpty, "empty"}, {CondLike, "like"}, {CondDWithin, "dwithin"}, -}; - -static const std::unordered_map op_map = {{OpOr, "or"}, {OpAnd, "and"}, {OpNot, "not"}}; - -static const std::unordered_map reqtotal_values = { - {ModeNoTotal, "disabled"}, {ModeAccurateTotal, "enabled"}, {ModeCachedTotal, "cached"}}; +constexpr static auto kJoinTypes = + frozen::make_unordered_map({{InnerJoin, "inner"}, {LeftJoin, "left"}, {OrInnerJoin, "orinner"}}); + +constexpr static auto kCondMap = frozen::make_unordered_map({ + {CondAny, "any"}, + {CondEq, "eq"}, + {CondLt, "lt"}, + {CondLe, "le"}, + {CondGt, "gt"}, + {CondGe, "ge"}, + {CondRange, "range"}, + {CondSet, "set"}, + {CondAllSet, "allset"}, + {CondEmpty, "empty"}, + {CondLike, "like"}, + {CondDWithin, "dwithin"}, +}); + +constexpr static auto kOpMap = frozen::make_unordered_map({{OpOr, "or"}, {OpAnd, "and"}, {OpNot, "not"}}); + +constexpr static auto kReqTotalValues = frozen::make_unordered_map( + {{ModeNoTotal, "disabled"}, {ModeAccurateTotal, "enabled"}, {ModeCachedTotal, "cached"}}); enum class QueryScope { Main, Subquery }; -template -std::string get(std::unordered_map const& m, const T& key) { +template +std::string_view get(frozen::unordered_map const& m, const T& key) { auto it = m.find(key); if (it != m.end()) return it->second; assertrx(it != m.end()); - return std::string(); + return std::string_view(); } static void encodeSorting(const SortingEntries& sortingEntries, JsonBuilder& builder) { @@ -132,15 +135,15 @@ static void encodeAggregationFunctions(const Query& query, JsonBuilder& builder) static void encodeJoinEntry(const QueryJoinEntry& joinEntry, JsonBuilder& builder) { builder.Put("left_field", joinEntry.LeftFieldName()); builder.Put("right_field", joinEntry.RightFieldName()); - builder.Put("cond", get(cond_map, joinEntry.Condition())); - builder.Put("op", get(op_map, joinEntry.Operation())); + builder.Put("cond", get(kCondMap, joinEntry.Condition())); + builder.Put("op", get(kOpMap, joinEntry.Operation())); } static void encodeSingleJoinQuery(const JoinedQuery& joinQuery, JsonBuilder& builder) { using namespace std::string_view_literals; auto node = builder.Object("join_query"sv); - node.Put("type", get(join_types, joinQuery.joinType)); + node.Put("type", get(kJoinTypes, joinQuery.joinType)); node.Put("namespace", joinQuery.NsName()); node.Put("limit", joinQuery.Limit()); node.Put("offset", joinQuery.Offset()); @@ -177,7 +180,7 @@ static void putValues(JsonBuilder& builder, const VariantArray& values) { static void encodeFilter(const QueryEntry& qentry, JsonBuilder& builder) { if (qentry.Distinct()) return; - builder.Put("cond", get(cond_map, CondType(qentry.Condition()))); + builder.Put("cond", get(kCondMap, CondType(qentry.Condition()))); builder.Put("field", qentry.FieldName()); putValues(builder, qentry.Values()); } @@ -224,7 +227,7 @@ static void toDsl(const Query& query, QueryScope scope, JsonBuilder& builder) { builder.Put("namespace", query.NsName()); builder.Put("limit", query.Limit()); builder.Put("offset", query.Offset()); - builder.Put("req_total", get(reqtotal_values, query.CalcTotal())); + builder.Put("req_total", get(kReqTotalValues, query.CalcTotal())); if (scope != QueryScope::Subquery) { builder.Put("explain", query.NeedExplain()); if (query.IsLocal()) { @@ -301,7 +304,7 @@ std::string toDsl(const Query& query) { void QueryEntries::toDsl(const_iterator it, const_iterator to, const Query& parentQuery, JsonBuilder& builder) { for (; it != to; ++it) { auto node = builder.Object(); - node.Put("op", dsl::get(dsl::op_map, it->operation)); + node.Put("op", dsl::get(dsl::kOpMap, it->operation)); it->Visit( [&node](const AlwaysFalse&) { logPrintf(LogTrace, "Not normalized query to dsl"); @@ -312,7 +315,7 @@ void QueryEntries::toDsl(const_iterator it, const_iterator to, const Query& pare node.Put("always", true); }, [&node, &parentQuery](const SubQueryEntry& sqe) { - node.Put("cond", dsl::get(dsl::cond_map, CondType(sqe.Condition()))); + node.Put("cond", dsl::get(dsl::kCondMap, CondType(sqe.Condition()))); { auto subquery = node.Object("subquery"); dsl::toDsl(parentQuery.GetSubQuery(sqe.QueryIndex()), dsl::QueryScope::Subquery, subquery); @@ -320,7 +323,7 @@ void QueryEntries::toDsl(const_iterator it, const_iterator to, const Query& pare dsl::putValues(node, sqe.Values()); }, [&node, &parentQuery](const SubQueryFieldEntry& sqe) { - node.Put("cond", dsl::get(dsl::cond_map, CondType(sqe.Condition()))); + node.Put("cond", dsl::get(dsl::kCondMap, CondType(sqe.Condition()))); node.Put("field", sqe.FieldName()); auto subquery = node.Object("subquery"); dsl::toDsl(parentQuery.GetSubQuery(sqe.QueryIndex()), dsl::QueryScope::Subquery, subquery); @@ -339,7 +342,7 @@ void QueryEntries::toDsl(const_iterator it, const_iterator to, const Query& pare dsl::encodeSingleJoinQuery(parentQuery.GetJoinQueries()[jqe.joinIndex], node); }, [&node](const BetweenFieldsQueryEntry& qe) { - node.Put("cond", dsl::get(dsl::cond_map, CondType(qe.Condition()))); + node.Put("cond", dsl::get(dsl::kCondMap, CondType(qe.Condition()))); node.Put("first_field", qe.LeftFieldName()); node.Put("second_field", qe.RightFieldName()); }); diff --git a/cpp_src/core/query/dsl/dslparser.cc b/cpp_src/core/query/dsl/dslparser.cc index 48c553be6..a4fbf3d2f 100644 --- a/cpp_src/core/query/dsl/dslparser.cc +++ b/cpp_src/core/query/dsl/dslparser.cc @@ -1,12 +1,13 @@ #include "dslparser.h" #include "core/cjson/jschemachecker.h" #include "core/query/query.h" -#include "estl/fast_hash_map.h" #include "gason/gason.h" #include "tools/errors.h" +#include "tools/frozen_str_tools.h" #include "tools/json2kv.h" #include "tools/jsontools.h" #include "tools/stringstools.h" +#include "vendor/frozen/unordered_map.h" namespace reindexer { using namespace gason; @@ -44,11 +45,13 @@ enum class EqualPosition { Positions }; enum class UpdateField { Name, Type, Values, IsArray }; enum class UpdateFieldType { Object, Expression, Value }; -// additional for parse root DSL fields -template -using fast_str_map = fast_hash_map; +template +constexpr auto MakeFastStrMap(std::pair const (&items)[N]) { + return frozen::make_unordered_map(items, frozen::nocase_hash_str{}, frozen::nocase_equal_str{}); +} -static const fast_str_map root_map = { +// additional for parse root DSL fields +constexpr static auto kRootMap = MakeFastStrMap({ {"namespace", Root::Namespace}, {"limit", Root::Limit}, {"offset", Root::Offset}, @@ -67,90 +70,105 @@ static const fast_str_map root_map = { {"type", Root::QueryType}, {"drop_fields", Root::DropFields}, {"update_fields", Root::UpdateFields}, -}; +}); // additional for parse field 'sort' - -static const fast_str_map sort_map = {{"desc", Sort::Desc}, {"field", Sort::Field}, {"values", Sort::Values}}; +constexpr static auto kSortMap = MakeFastStrMap({{"desc", Sort::Desc}, {"field", Sort::Field}, {"values", Sort::Values}}); // additional for parse field 'joined' +constexpr static auto joins_map = MakeFastStrMap({{"type", JoinRoot::Type}, + {"namespace", JoinRoot::Namespace}, + {"filters", JoinRoot::Filters}, + {"sort", JoinRoot::Sort}, + {"limit", JoinRoot::Limit}, + {"offset", JoinRoot::Offset}, + {"on", JoinRoot::On}, + {"select_filter", JoinRoot::SelectFilter}}); -static const fast_str_map joins_map = {{"type", JoinRoot::Type}, {"namespace", JoinRoot::Namespace}, - {"filters", JoinRoot::Filters}, {"sort", JoinRoot::Sort}, - {"limit", JoinRoot::Limit}, {"offset", JoinRoot::Offset}, - {"on", JoinRoot::On}, {"select_filter", JoinRoot::SelectFilter}}; +constexpr static auto joined_entry_map = MakeFastStrMap( + {{"left_field", JoinEntry::LeftField}, {"right_field", JoinEntry::RightField}, {"cond", JoinEntry::Cond}, {"op", JoinEntry::Op}}); -static const fast_str_map joined_entry_map = { - {"left_field", JoinEntry::LeftField}, {"right_field", JoinEntry::RightField}, {"cond", JoinEntry::Cond}, {"op", JoinEntry::Op}}; - -static const fast_str_map join_types = {{"inner", InnerJoin}, {"left", LeftJoin}, {"orinner", OrInnerJoin}}; +constexpr static auto join_types = MakeFastStrMap({{"inner", InnerJoin}, {"left", LeftJoin}, {"orinner", OrInnerJoin}}); // additionalfor parse field 'filters' - -static const fast_str_map filter_map = {{"cond", Filter::Cond}, - {"op", Filter::Op}, - {"field", Filter::Field}, - {"value", Filter::Value}, - {"filters", Filter::Filters}, - {"join_query", Filter::JoinQuery}, - {"first_field", Filter::FirstField}, - {"second_field", Filter::SecondField}, - {"equal_positions", Filter::EqualPositions}, - {"subquery", Filter::SubQuery}, - {"always", Filter::Always}}; +constexpr static auto filter_map = MakeFastStrMap({{"cond", Filter::Cond}, + {"op", Filter::Op}, + {"field", Filter::Field}, + {"value", Filter::Value}, + {"filters", Filter::Filters}, + {"join_query", Filter::JoinQuery}, + {"first_field", Filter::FirstField}, + {"second_field", Filter::SecondField}, + {"equal_positions", Filter::EqualPositions}, + {"subquery", Filter::SubQuery}, + {"always", Filter::Always}}); // additional for 'filter::cond' field - -static const fast_str_map cond_map = { - {"any", CondAny}, {"eq", CondEq}, {"lt", CondLt}, {"le", CondLe}, {"gt", CondGt}, - {"ge", CondGe}, {"range", CondRange}, {"set", CondSet}, {"allset", CondAllSet}, {"empty", CondEmpty}, - {"match", CondEq}, {"like", CondLike}, {"dwithin", CondDWithin}, -}; - -static const fast_str_map op_map = {{"or", OpOr}, {"and", OpAnd}, {"not", OpNot}}; +constexpr static auto cond_map = MakeFastStrMap({ + {"any", CondAny}, + {"eq", CondEq}, + {"lt", CondLt}, + {"le", CondLe}, + {"gt", CondGt}, + {"ge", CondGe}, + {"range", CondRange}, + {"set", CondSet}, + {"allset", CondAllSet}, + {"empty", CondEmpty}, + {"match", CondEq}, + {"like", CondLike}, + {"dwithin", CondDWithin}, +}); + +constexpr static auto kOpMap = MakeFastStrMap({{"or", OpOr}, {"and", OpAnd}, {"not", OpNot}}); // additional for 'Root::ReqTotal' field -static const fast_str_map reqtotal_values = { - {"disabled", ModeNoTotal}, {"enabled", ModeAccurateTotal}, {"cached", ModeCachedTotal}}; +constexpr static auto kReqTotalValues = + MakeFastStrMap({{"disabled", ModeNoTotal}, {"enabled", ModeAccurateTotal}, {"cached", ModeCachedTotal}}); // additional for 'Root::Aggregations' field - -static const fast_str_map aggregation_map = {{"fields", Aggregation::Fields}, - {"type", Aggregation::Type}, - {"sort", Aggregation::Sort}, - {"limit", Aggregation::Limit}, - {"offset", Aggregation::Offset}}; -static const fast_str_map aggregation_types = { - {"sum", AggSum}, {"avg", AggAvg}, {"max", AggMax}, {"min", AggMin}, - {"facet", AggFacet}, {"distinct", AggDistinct}, {"count", AggCount}, {"count_cached", AggCountCached}, -}; +constexpr static auto kAggregationMap = MakeFastStrMap({{"fields", Aggregation::Fields}, + {"type", Aggregation::Type}, + {"sort", Aggregation::Sort}, + {"limit", Aggregation::Limit}, + {"offset", Aggregation::Offset}}); +constexpr static auto kAggregationTypes = MakeFastStrMap({ + {"sum", AggSum}, + {"avg", AggAvg}, + {"max", AggMax}, + {"min", AggMin}, + {"facet", AggFacet}, + {"distinct", AggDistinct}, + {"count", AggCount}, + {"count_cached", AggCountCached}, +}); // additionalfor parse field 'equation_positions' -static const fast_str_map equationPosition_map = {{"positions", EqualPosition::Positions}}; +constexpr static auto kEquationPositionMap = MakeFastStrMap({{"positions", EqualPosition::Positions}}); // additional for 'Root::QueryType' field -static const fast_str_map query_types = { +constexpr static auto kQueryTypes = MakeFastStrMap({ {"select", QuerySelect}, {"update", QueryUpdate}, {"delete", QueryDelete}, {"truncate", QueryTruncate}, -}; +}); // additional for 'Root::UpdateField' field -static const fast_str_map update_field_map = { +constexpr static auto kUpdateFieldMap = MakeFastStrMap({ {"name", UpdateField::Name}, {"type", UpdateField::Type}, {"values", UpdateField::Values}, {"is_array", UpdateField::IsArray}, -}; +}); // additional for 'Root::UpdateFieldType' field -static const fast_str_map update_field_type_map = { +constexpr static auto kUpdateFieldTypeMap = MakeFastStrMap({ {"object", UpdateFieldType::Object}, {"expression", UpdateFieldType::Expression}, {"value", UpdateFieldType::Value}, -}; +}); static bool checkTag(const JsonValue& val, JsonTag tag) noexcept { return val.getTag() == tag; } @@ -164,8 +182,9 @@ void checkJsonValueType(const JsonValue& val, std::string_view name, JsonTags... if (!checkTag(val, possibleTags...)) throw Error(errParseJson, "Wrong type of field '%s'", name); } -template -T get(fast_str_map const& m, std::string_view name, std::string_view mapName) { +template +T get(frozen::unordered_map const& m, std::string_view name, + std::string_view mapName) { auto it = m.find(name); if (it == m.end()) { throw Error(errParseDSL, "Element [%s] not allowed in object of type [%s]", name, mapName); @@ -183,30 +202,26 @@ void parseStringArray(const JsonValue& stringArray, Arr& array) { } template -void parseValues(const JsonValue& values, Array& kvs) { +void parseValues(const JsonValue& values, Array& kvs, std::string_view fieldName) { if (values.getTag() == JSON_ARRAY) { + uint32_t objectsCount = 0; for (const auto& elem : values) { Variant kv; if (elem.value.getTag() == JSON_OBJECT) { kv = Variant(stringifyJson(elem)); + ++objectsCount; } else if (elem.value.getTag() != JSON_NULL) { - kv = jsonValue2Variant(elem.value, KeyValueType::Undefined{}); + kv = jsonValue2Variant(elem.value, KeyValueType::Undefined{}, fieldName); kv.EnsureHold(); } - if (!kvs.empty() && !kvs.back().Type().IsSame(kv.Type())) { - if (kvs.size() != 1 || !((kvs[0].Type().template Is() && - (kv.Type().Is() || kv.Type().Is() || - kv.Type().Is())) || - (kv.Type().Is() && (kvs[0].Type().template Is() || - kvs[0].Type().template Is() || - kvs[0].Type().template Is())))) { - throw Error(errParseJson, "Array of filter values must be homogeneous."); - } - } kvs.emplace_back(std::move(kv)); } + + if ((0 < objectsCount) && (objectsCount < kvs.size())) { + throw Error(errParseJson, "Array with objects must be homogeneous"); + } } else if (values.getTag() != JSON_NULL) { - Variant kv(jsonValue2Variant(values, KeyValueType::Undefined{})); + Variant kv(jsonValue2Variant(values, KeyValueType::Undefined{}, fieldName)); kv.EnsureHold(); kvs.emplace_back(std::move(kv)); } @@ -220,7 +235,7 @@ static void parseSortEntry(const JsonValue& entry, SortingEntries& sortingEntrie for (const auto& subelement : entry) { auto& v = subelement.value; std::string_view name = subelement.key; - switch (get(sort_map, name, "sort"sv)) { + switch (get(kSortMap, name, "sort"sv)) { case Sort::Desc: if ((v.getTag() != JSON_TRUE) && (v.getTag() != JSON_FALSE)) throw Error(errParseJson, "Wrong type of field '%s'", name); sortingEntry.desc = (v.getTag() == JSON_TRUE); @@ -235,7 +250,7 @@ static void parseSortEntry(const JsonValue& entry, SortingEntries& sortingEntrie if (!sortingEntries.empty()) { throw Error(errParseJson, "Forced sort order is allowed for the first sorting entry only"); } - parseValues(v, forcedSortOrder); + parseValues(v, forcedSortOrder, name); break; } } @@ -279,11 +294,11 @@ static void parseFilter(const JsonValue& filter, Query& q, std::vector(op_map, v.toString(), "operation enum"sv); + op = get(kOpMap, v.toString(), "operation enum"sv); break; case Filter::Value: - parseValues(v, values); + parseValues(v, values, name); break; case Filter::JoinQuery: @@ -409,7 +424,7 @@ static void parseJoinedEntries(const JsonValue& joinEntries, JoinedQuery& qjoin) break; case JoinEntry::Op: checkJsonValueType(value, name, JSON_STRING); - op = get(op_map, value.toString(), "operation enum"sv); + op = get(kOpMap, value.toString(), "operation enum"sv); break; } } @@ -493,7 +508,7 @@ static void parseAggregation(const JsonValue& aggregation, Query& query) { for (const auto& element : aggregation) { auto& value = element.value; std::string_view name = element.key; - switch (get(aggregation_map, name, "aggregations"sv)) { + switch (get(kAggregationMap, name, "aggregations"sv)) { case Aggregation::Fields: checkJsonValueType(value, name, JSON_ARRAY); for (const auto& subElem : value) { @@ -503,7 +518,7 @@ static void parseAggregation(const JsonValue& aggregation, Query& query) { break; case Aggregation::Type: checkJsonValueType(value, name, JSON_STRING); - type = get(aggregation_types, value.toString(), "aggregation type enum"sv); + type = get(kAggregationTypes, value.toString(), "aggregation type enum"sv); if (!query.CanAddAggregation(type)) { throw Error(errConflict, kAggregationWithSelectFieldsMsgError); } @@ -536,7 +551,7 @@ void parseEqualPositions(const JsonValue& dsl, std::vector(equationPosition_map, name, "equal_positions"sv)) { + switch (get(kEquationPositionMap, name, "equal_positions"sv)) { case EqualPosition::Positions: { EqualPosition_t ep; for (const auto& f : value) { @@ -564,14 +579,14 @@ static void parseUpdateFields(const JsonValue& updateFields, Query& query) { for (const auto& v : field) { auto& value = v.value; std::string_view name = v.key; - switch (get(update_field_map, name, "update_fields"sv)) { + switch (get(kUpdateFieldMap, name, "update_fields"sv)) { case UpdateField::Name: checkJsonValueType(value, name, JSON_STRING); fieldName.assign(value.sval.data(), value.sval.size()); break; case UpdateField::Type: { checkJsonValueType(value, name, JSON_STRING); - switch (get(update_field_type_map, value.toString(), "update_fields_type"sv)) { + switch (get(kUpdateFieldTypeMap, value.toString(), "update_fields_type"sv)) { case UpdateFieldType::Object: isObject = true; break; @@ -590,12 +605,12 @@ static void parseUpdateFields(const JsonValue& updateFields, Query& query) { break; case UpdateField::Values: checkJsonValueType(value, name, JSON_ARRAY); - parseValues(value, values); + parseValues(value, values, name); break; } } if (isExpression && (values.size() != 1 || !values.front().Type().template Is())) - throw Error(errParseDSL, "The array \"values\" must contain only a string type value for the type \"expression\""); + throw Error(errParseDSL, R"(The array "values" must contain only a string type value for the type "expression")"); if (isObject) { query.SetObject(fieldName, std::move(values)); @@ -614,7 +629,7 @@ void parse(const JsonValue& root, Query& q) { for (const auto& elem : root) { auto& v = elem.value; auto name = elem.key; - switch (get(root_map, name, "root"sv)) { + switch (get(kRootMap, name, "root"sv)) { case Root::Namespace: checkJsonValueType(v, name, JSON_STRING); q.SetNsName(v.toString()); @@ -658,7 +673,7 @@ void parse(const JsonValue& root, Query& q) { break; case Root::ReqTotal: checkJsonValueType(v, name, JSON_STRING); - q.CalcTotal(get(reqtotal_values, v.toString(), "req_total enum"sv)); + q.CalcTotal(get(kReqTotalValues, v.toString(), "req_total enum"sv)); break; case Root::Aggregations: checkJsonValueType(v, name, JSON_ARRAY); @@ -687,7 +702,7 @@ void parse(const JsonValue& root, Query& q) { throw Error(errParseDSL, "Unsupported old DSL format. Equal positions should be in filters."); case Root::QueryType: checkJsonValueType(v, name, JSON_STRING); - q.type_ = get(query_types, v.toString(), "query_type"sv); + q.type_ = get(kQueryTypes, v.toString(), "query_type"sv); break; case Root::DropFields: checkJsonValueType(v, name, JSON_ARRAY); diff --git a/cpp_src/core/query/dsl/dslparser.h b/cpp_src/core/query/dsl/dslparser.h index ef8df2bbf..84311cfc7 100644 --- a/cpp_src/core/query/dsl/dslparser.h +++ b/cpp_src/core/query/dsl/dslparser.h @@ -9,7 +9,7 @@ class Query; namespace dsl { -[[nodiscard]] Error Parse(std::string_view dsl, Query& q); +Error Parse(std::string_view dsl, Query& q); } // namespace dsl } // namespace reindexer diff --git a/cpp_src/core/query/expressionevaluator.cc b/cpp_src/core/query/expressionevaluator.cc index 439703ef0..050656460 100644 --- a/cpp_src/core/query/expressionevaluator.cc +++ b/cpp_src/core/query/expressionevaluator.cc @@ -47,8 +47,8 @@ void ExpressionEvaluator::throwUnexpectedTokenError(tokenizer& parser, const tok throw Error(errParams, kWrongFieldTypeError, outTok.text()); } -ExpressionEvaluator::PrimaryToken ExpressionEvaluator::getPrimaryToken(tokenizer& parser, const PayloadValue& v, token& outTok, - const NsContext& ctx) { +ExpressionEvaluator::PrimaryToken ExpressionEvaluator::getPrimaryToken(tokenizer& parser, const PayloadValue& v, StringAllowed strAllowed, + token& outTok, const NsContext& ctx) { outTok = parser.next_token(); if (outTok.text() == "("sv) { const double val = performSumAndSubtracting(parser, v, ctx); @@ -75,7 +75,13 @@ ExpressionEvaluator::PrimaryToken ExpressionEvaluator::getPrimaryToken(tokenizer case TokenName: return handleTokenName(parser, v, outTok, ctx); case TokenString: - throwUnexpectedTokenError(parser, outTok); + if (strAllowed == StringAllowed::Yes) { + arrayValues_.MarkArray(); + arrayValues_.emplace_back(token2kv(outTok, parser, false)); + return {.value = std::nullopt, .type = PrimaryToken::Type::Array}; + } else { + throwUnexpectedTokenError(parser, outTok); + } case TokenEnd: case TokenOp: case TokenSymbol: @@ -195,7 +201,7 @@ void ExpressionEvaluator::handleCommand(tokenizer& parser, const PayloadValue& v VariantArray resultArr; token valueToken; - auto trr = getPrimaryToken(parser, v, valueToken, ctx); + auto trr = getPrimaryToken(parser, v, StringAllowed::No, valueToken, ctx); if rx_unlikely (trr.type != PrimaryToken::Type::Null) { if rx_unlikely (trr.type != PrimaryToken::Type::Array) { throw Error(errParams, "Only an array field is expected as first parameter of command 'array_remove_once/array_remove'"); @@ -212,10 +218,12 @@ void ExpressionEvaluator::handleCommand(tokenizer& parser, const PayloadValue& v } // parse list of delete items - auto array = getPrimaryToken(parser, v, valueToken, ctx); - if rx_unlikely (array.type != PrimaryToken::Type::Null) { - if rx_unlikely (array.type != PrimaryToken::Type::Array) { - throw Error(errParams, "Expecting array as command parameter: '%s'", valueToken.text()); + auto val = getPrimaryToken(parser, v, StringAllowed::Yes, valueToken, ctx); + if rx_unlikely (val.type != PrimaryToken::Type::Null) { + if ((val.type != PrimaryToken::Type::Array) && (val.type != PrimaryToken::Type::Scalar)) { + throw Error(errParams, "Expecting array or scalar as command parameter: '%s'", valueToken.text()); + } else if ((val.type == PrimaryToken::Type::Scalar) && val.value.has_value()) { + arrayValues_.emplace_back(Variant(val.value.value())); } } @@ -230,7 +238,7 @@ void ExpressionEvaluator::handleCommand(tokenizer& parser, const PayloadValue& v if (cmd == Command::ArrayRemoveOnce) { // remove elements from array once auto it = std::find_if(values.begin(), values.end(), [&item](const auto& elem) { - return item.RelaxCompare(elem) == ComparationResult::Eq; + return item.RelaxCompare(elem) == ComparationResult::Eq; }); if (it != values.end()) { values.erase(it); @@ -239,7 +247,7 @@ void ExpressionEvaluator::handleCommand(tokenizer& parser, const PayloadValue& v // remove elements from array values.erase(std::remove_if(values.begin(), values.end(), [&item](const auto& elem) { - return item.RelaxCompare(elem) == ComparationResult::Eq; + return item.RelaxCompare(elem) == ComparationResult::Eq; }), values.end()); } @@ -250,7 +258,7 @@ void ExpressionEvaluator::handleCommand(tokenizer& parser, const PayloadValue& v double ExpressionEvaluator::performArrayConcatenation(tokenizer& parser, const PayloadValue& v, token& tok, const NsContext& ctx) { token valueToken; - auto left = getPrimaryToken(parser, v, valueToken, ctx); + auto left = getPrimaryToken(parser, v, StringAllowed::No, valueToken, ctx); tok = parser.peek_token(); switch (left.type) { case PrimaryToken::Type::Scalar: @@ -284,7 +292,7 @@ double ExpressionEvaluator::performArrayConcatenation(tokenizer& parser, const P throw Error(errParams, "Unable to mix arrays concatenation and arithmetic operations. Got token: '%s'", tok.text()); } state_ = StateArrayConcat; - auto right = getPrimaryToken(parser, v, valueToken, ctx); + auto right = getPrimaryToken(parser, v, StringAllowed::No, valueToken, ctx); if rx_unlikely (right.type == PrimaryToken::Type::Scalar) { throw Error(errParams, kScalarsInConcatenationError, valueToken.text()); } diff --git a/cpp_src/core/query/expressionevaluator.h b/cpp_src/core/query/expressionevaluator.h index 1b8c6d234..1153142e7 100644 --- a/cpp_src/core/query/expressionevaluator.h +++ b/cpp_src/core/query/expressionevaluator.h @@ -26,7 +26,9 @@ class ExpressionEvaluator { Type type; }; - [[nodiscard]] PrimaryToken getPrimaryToken(tokenizer& parser, const PayloadValue& v, token& outTok, const NsContext& ctx); + enum class StringAllowed : bool { No = false, Yes = true }; + [[nodiscard]] PrimaryToken getPrimaryToken(tokenizer& parser, const PayloadValue& v, StringAllowed strAllowed, token& outTok, + const NsContext& ctx); [[nodiscard]] PrimaryToken handleTokenName(tokenizer& parser, const PayloadValue& v, token& outTok, const NsContext& ctx); [[nodiscard]] double performSumAndSubtracting(tokenizer& parser, const PayloadValue& v, const NsContext& ctx); [[nodiscard]] double performMultiplicationAndDivision(tokenizer& parser, const PayloadValue& v, token& lastTok, const NsContext& ctx); diff --git a/cpp_src/core/query/query.h b/cpp_src/core/query/query.h index cc20adde1..b93e79eba 100644 --- a/cpp_src/core/query/query.h +++ b/cpp_src/core/query/query.h @@ -66,7 +66,7 @@ class Query { /// Parses JSON dsl set. /// @param dsl - dsl set. /// @return always returns errOk or throws an exception. - [[nodiscard]] Error FromJSON(std::string_view dsl); + Error FromJSON(std::string_view dsl); /// returns structure of a query in JSON dsl format [[nodiscard]] std::string GetJSON() const; @@ -163,7 +163,7 @@ class Query { /// @param l - list of values to be compared according to the order /// of indexes in composite index name. /// There can be maximum 2 VariantArray objects in l: in case of CondRange condition, - /// in all other cases amount of elements in l would be striclty equal to 1. + /// in all other cases amount of elements in l would be strictly equal to 1. /// For example, composite index name is "bookid+price", so l[0][0] (and l[1][0] /// in case of CondRange) belongs to "bookid" and l[0][1] (and l[1][1] in case of CondRange) /// belongs to "price" indexes. @@ -228,7 +228,7 @@ class Query { } else { q.checkSubQueryWithData(); if (!q.selectFilter_.empty() && !q.HasLimit() && !q.HasOffset()) { - // Transforms main query condition into subquerie's condition + // Converts main query condition to subquery condition q.sortingEntries_.clear(); q.Where(q.selectFilter_[0], cond, std::move(values)); q.selectFilter_.clear(); @@ -294,7 +294,7 @@ class Query { /// Sets a new value for a field. /// @param field - field name. /// @param value - new value. - /// @param hasExpressions - true: value has expresions in it + /// @param hasExpressions - true: value has expressions in it template > * = nullptr> Query &Set(Str &&field, ValueType value, bool hasExpressions = false) & { return Set(std::forward(field), {value}, hasExpressions); @@ -306,7 +306,7 @@ class Query { /// Sets a new value for a field. /// @param field - field name. /// @param l - new value. - /// @param hasExpressions - true: value has expresions in it + /// @param hasExpressions - true: value has expressions in it template > * = nullptr> Query &Set(Str &&field, std::initializer_list l, bool hasExpressions = false) & { VariantArray value; @@ -321,7 +321,7 @@ class Query { /// Sets a new value for a field. /// @param field - field name. /// @param l - new value. - /// @param hasExpressions - true: value has expresions in it + /// @param hasExpressions - true: value has expressions in it template > * = nullptr> Query &Set(Str &&field, const std::vector &l, bool hasExpressions = false) & { VariantArray value; @@ -336,7 +336,7 @@ class Query { /// Sets a new value for a field. /// @param field - field name. /// @param value - new value. - /// @param hasExpressions - true: value has expresions in it + /// @param hasExpressions - true: value has expressions in it template > * = nullptr> Query &Set(Str &&field, VariantArray value, bool hasExpressions = false) & { updateFields_.emplace_back(std::forward(field), std::move(value), FieldModeSet, hasExpressions); @@ -349,7 +349,7 @@ class Query { /// Sets a value for a field as an object. /// @param field - field name. /// @param value - new value. - /// @param hasExpressions - true: value has expresions in it + /// @param hasExpressions - true: value has expressions in it template > * = nullptr> Query &SetObject(Str &&field, ValueType value, bool hasExpressions = false) & { return SetObject(std::forward(field), {value}, hasExpressions); @@ -361,7 +361,7 @@ class Query { /// Sets a new value for a field as an object. /// @param field - field name. /// @param l - new value. - /// @param hasExpressions - true: value has expresions in it + /// @param hasExpressions - true: value has expressions in it template > * = nullptr> Query &SetObject(Str &&field, std::initializer_list l, bool hasExpressions = false) & { VariantArray value; @@ -376,7 +376,7 @@ class Query { /// Sets a new value for a field as an object. /// @param field - field name. /// @param l - new value. - /// @param hasExpressions - true: value has expresions in it + /// @param hasExpressions - true: value has expressions in it template > * = nullptr> Query &SetObject(Str &&field, const std::vector &l, bool hasExpressions = false) & { VariantArray value; @@ -391,7 +391,7 @@ class Query { /// Sets a value for a field as an object. /// @param field - field name. /// @param value - new value. - /// @param hasExpressions - true: value has expresions in it + /// @param hasExpressions - true: value has expressions in it template > * = nullptr> Query &SetObject(Str &&field, VariantArray value, bool hasExpressions = false) & { for (auto &it : value) { @@ -642,7 +642,7 @@ class Query { } /// Performs distinct for a certain index. - /// @param indexName - name of index for distict operation. + /// @param indexName - name of index for distinct operation. template > * = nullptr> Query &Distinct(Str &&indexName) & { if (!strEmpty(indexName)) { @@ -972,7 +972,7 @@ class Query { std::vector mergeQueries_; /// List of merge queries. std::vector subQueries_; h_vector selectFilter_; /// List of columns in a final result set. - bool local_ = false; /// Local query if true + bool local_ = false; /// Local query if true bool withRank_ = false; StrictMode strictMode_ = StrictModeNotSet; /// Strict mode. int debugLevel_ = 0; /// Debug level. @@ -989,7 +989,7 @@ class JoinedQuery : public Query { const std::string &RightNsName() const noexcept { return NsName(); } JoinType joinType{JoinType::LeftJoin}; /// Default join type. - h_vector joinEntries_; /// Condition for join. Filled in each subqueries, empty in root query + h_vector joinEntries_; /// Condition for join. Filled in each subqueries, empty in root query }; template diff --git a/cpp_src/core/query/queryentry.cc b/cpp_src/core/query/queryentry.cc index 6547a29f9..8a32d917b 100644 --- a/cpp_src/core/query/queryentry.cc +++ b/cpp_src/core/query/queryentry.cc @@ -56,12 +56,9 @@ bool QueryField::operator==(const QueryField &other) const noexcept { compositeFieldsTypes_.size() != other.compositeFieldsTypes_.size()) { return false; } - for (size_t i = 0, s = compositeFieldsTypes_.size(); i < s; ++i) { - if (!compositeFieldsTypes_[i].IsSame(other.compositeFieldsTypes_[i])) { - return false; - } - } - return true; + return std::equal( + compositeFieldsTypes_.begin(), compositeFieldsTypes_.end(), other.compositeFieldsTypes_.begin(), + [](const CompositeTypesVecT::value_type &l, const CompositeTypesVecT::value_type &r) noexcept { return l.IsSame(r); }); } void QueryField::SetField(FieldsSet &&fields) & { @@ -73,7 +70,7 @@ void QueryField::SetField(FieldsSet &&fields) & { } static void checkIndexData([[maybe_unused]] int idxNo, [[maybe_unused]] const FieldsSet &fields, KeyValueType fieldType, - [[maybe_unused]] const std::vector &compositeFieldsTypes) { + [[maybe_unused]] const QueryField::CompositeTypesVecT &compositeFieldsTypes) { assertrx_throw(idxNo >= 0); if (fieldType.Is()) { assertrx_throw(fields.size() == compositeFieldsTypes.size()); @@ -84,7 +81,7 @@ static void checkIndexData([[maybe_unused]] int idxNo, [[maybe_unused]] const Fi } void QueryField::SetIndexData(int idxNo, FieldsSet &&fields, KeyValueType fieldType, KeyValueType selectType, - std::vector &&compositeFieldsTypes) & { + QueryField::CompositeTypesVecT &&compositeFieldsTypes) & { checkIndexData(idxNo, fields, fieldType, compositeFieldsTypes); idxNo_ = idxNo; fieldsSet_ = std::move(fields); @@ -841,7 +838,7 @@ void QueryEntries::dumpEqualPositions(size_t level, WrSerializer &ser, const Equ for (size_t i = 0; i < level; ++i) { ser << " "; } - ser << "equal_poisition("; + ser << "equal_positions("; for (size_t i = 0, s = eq.size(); i < s; ++i) { if (i != 0) ser << ", "; ser << eq[i]; diff --git a/cpp_src/core/query/queryentry.h b/cpp_src/core/query/queryentry.h index 71c612d74..eaa8183a7 100644 --- a/cpp_src/core/query/queryentry.h +++ b/cpp_src/core/query/queryentry.h @@ -34,6 +34,8 @@ struct JoinQueryEntry { class QueryField { public: + using CompositeTypesVecT = h_vector; + template explicit QueryField(Str &&fieldName) noexcept : fieldName_{std::forward(fieldName)} {} QueryField(std::string &&fieldName, int idxNo, FieldsSet fields, KeyValueType fieldType, @@ -52,11 +54,11 @@ class QueryField { [[nodiscard]] const std::string &FieldName() const & noexcept { return fieldName_; } [[nodiscard]] KeyValueType FieldType() const noexcept { return fieldType_; } [[nodiscard]] KeyValueType SelectType() const noexcept { return selectType_; } - [[nodiscard]] const std::vector &CompositeFieldsTypes() const & noexcept { return compositeFieldsTypes_; } + [[nodiscard]] const CompositeTypesVecT &CompositeFieldsTypes() const & noexcept { return compositeFieldsTypes_; } [[nodiscard]] bool HaveEmptyField() const noexcept; void SetField(FieldsSet &&fields) &; void SetIndexData(int idxNo, FieldsSet &&fields, KeyValueType fieldType, KeyValueType selectType, - std::vector &&compositeFieldsTypes) &; + CompositeTypesVecT &&compositeFieldsTypes) &; QueryField &operator=(const QueryField &) = delete; auto Fields() const && = delete; @@ -69,7 +71,7 @@ class QueryField { FieldsSet fieldsSet_; KeyValueType fieldType_{KeyValueType::Undefined{}}; KeyValueType selectType_{KeyValueType::Undefined{}}; - std::vector compositeFieldsTypes_; + CompositeTypesVecT compositeFieldsTypes_; }; enum class VerifyQueryEntryFlags : unsigned { null = 0u, ignoreEmptyValues = 1u }; @@ -181,7 +183,7 @@ class BetweenFieldsQueryEntry { BetweenFieldsQueryEntry(StrL &&fstIdx, CondType cond, StrR &&sndIdx) : leftField_{std::forward(fstIdx)}, rightField_{std::forward(sndIdx)}, condition_{cond} { if (condition_ == CondAny || condition_ == CondEmpty || condition_ == CondDWithin) { - throw Error{errLogic, "Condition '%s' is inapplicable between two fields", std::string{CondTypeToStr(condition_)}}; + throw Error{errLogic, "Condition '%s' is inapplicable between two fields", CondTypeToStr(condition_)}; } } @@ -197,8 +199,10 @@ class BetweenFieldsQueryEntry { [[nodiscard]] const FieldsSet &RightFields() const & noexcept { return rightField_.Fields(); } [[nodiscard]] KeyValueType LeftFieldType() const noexcept { return leftField_.FieldType(); } [[nodiscard]] KeyValueType RightFieldType() const noexcept { return rightField_.FieldType(); } - [[nodiscard]] const std::vector &LeftCompositeFieldsTypes() const & noexcept { return leftField_.CompositeFieldsTypes(); } - [[nodiscard]] const std::vector &RightCompositeFieldsTypes() const & noexcept { + [[nodiscard]] const QueryField::CompositeTypesVecT &LeftCompositeFieldsTypes() const & noexcept { + return leftField_.CompositeFieldsTypes(); + } + [[nodiscard]] const QueryField::CompositeTypesVecT &RightCompositeFieldsTypes() const & noexcept { return rightField_.CompositeFieldsTypes(); } [[nodiscard]] const QueryField &LeftFieldData() const & noexcept { return leftField_; } @@ -304,7 +308,7 @@ class UpdateEntry { } bool operator==(const UpdateEntry &) const noexcept; bool operator!=(const UpdateEntry &obj) const noexcept { return !operator==(obj); } - std::string const &Column() const noexcept { return column_; } + std::string_view Column() const noexcept { return column_; } VariantArray const &Values() const noexcept { return values_; } VariantArray &Values() noexcept { return values_; } FieldModifyMode Mode() const noexcept { return mode_; } @@ -333,8 +337,10 @@ class QueryJoinEntry { [[nodiscard]] const FieldsSet &RightFields() const & noexcept { return rightField_.Fields(); } [[nodiscard]] KeyValueType LeftFieldType() const noexcept { return leftField_.FieldType(); } [[nodiscard]] KeyValueType RightFieldType() const noexcept { return rightField_.FieldType(); } - [[nodiscard]] const std::vector &LeftCompositeFieldsTypes() const & noexcept { return leftField_.CompositeFieldsTypes(); } - [[nodiscard]] const std::vector &RightCompositeFieldsTypes() const & noexcept { + [[nodiscard]] const QueryField::CompositeTypesVecT &LeftCompositeFieldsTypes() const & noexcept { + return leftField_.CompositeFieldsTypes(); + } + [[nodiscard]] const QueryField::CompositeTypesVecT &RightCompositeFieldsTypes() const & noexcept { return rightField_.CompositeFieldsTypes(); } [[nodiscard]] OpType Operation() const noexcept { return op_; } @@ -347,11 +353,11 @@ class QueryJoinEntry { [[nodiscard]] const QueryField &RightFieldData() const & noexcept { return rightField_; } [[nodiscard]] QueryField &RightFieldData() & noexcept { return rightField_; } void SetLeftIndexData(int idxNo, FieldsSet &&fields, KeyValueType fieldType, KeyValueType selectType, - std::vector &&compositeFieldsTypes) & { + QueryField::CompositeTypesVecT &&compositeFieldsTypes) & { leftField_.SetIndexData(idxNo, std::move(fields), fieldType, selectType, std::move(compositeFieldsTypes)); } void SetRightIndexData(int idxNo, FieldsSet &&fields, KeyValueType fieldType, KeyValueType selectType, - std::vector &&compositeFieldsTypes) & { + QueryField::CompositeTypesVecT &&compositeFieldsTypes) & { rightField_.SetIndexData(idxNo, std::move(fields), fieldType, selectType, std::move(compositeFieldsTypes)); } void SetLeftField(FieldsSet &&fields) & { leftField_.SetField(std::move(fields)); } diff --git a/cpp_src/core/query/sql/sqlencoder.cc b/cpp_src/core/query/sql/sqlencoder.cc index ac5e17867..a457b99c7 100644 --- a/cpp_src/core/query/sql/sqlencoder.cc +++ b/cpp_src/core/query/sql/sqlencoder.cc @@ -262,7 +262,7 @@ WrSerializer &SQLEncoder::GetSQL(WrSerializer &ser, bool stripArgs) const { return ser; } -static const char *opNames[] = {"-", "OR", "AND", "AND NOT"}; +constexpr static std::string_view kOpNames[] = {"-", "OR", "AND", "AND NOT"}; template static void dumpCondWithValues(WrSerializer &ser, std::string_view fieldName, CondType cond, const VariantArray &values, bool stripArgs) { @@ -340,14 +340,14 @@ void SQLEncoder::dumpWhereEntries(QueryEntries::const_iterator from, QueryEntrie }, [&](const SubQueryEntry &sqe) { if (encodedEntries) { - ser << opNames[op] << ' '; + ser << kOpNames[op] << ' '; } dumpCondWithValues(ser, '(' + query_.GetSubQuery(sqe.QueryIndex()).GetSQL(stripArgs) + ')', sqe.Condition(), sqe.Values(), stripArgs); }, [&](const SubQueryFieldEntry &sqe) { if (encodedEntries) { - ser << opNames[op] << ' '; + ser << kOpNames[op] << ' '; } ser << sqe.FieldName() << ' ' << sqe.Condition() << " ("; SQLEncoder{query_.GetSubQuery(sqe.QueryIndex())}.GetSQL(ser, stripArgs); @@ -355,7 +355,7 @@ void SQLEncoder::dumpWhereEntries(QueryEntries::const_iterator from, QueryEntrie }, [&](const QueryEntriesBracket &bracket) { if (encodedEntries) { - ser << opNames[op] << ' '; + ser << kOpNames[op] << ' '; } ser << '('; dumpWhereEntries(it.cbegin(), it.cend(), ser, stripArgs); @@ -364,19 +364,19 @@ void SQLEncoder::dumpWhereEntries(QueryEntries::const_iterator from, QueryEntrie }, [&](const QueryEntry &entry) { if (encodedEntries) { - ser << opNames[op] << ' '; + ser << kOpNames[op] << ' '; } dumpCondWithValues(ser, entry.FieldName(), entry.Condition(), entry.Values(), stripArgs); }, [&](const JoinQueryEntry &jqe) { if (encodedEntries && query_.GetJoinQueries()[jqe.joinIndex].joinType != JoinType::OrInnerJoin) { - ser << opNames[op] << ' '; + ser << kOpNames[op] << ' '; } SQLEncoder(query_).DumpSingleJoinQuery(jqe.joinIndex, ser, stripArgs); }, [&](const BetweenFieldsQueryEntry &entry) { if (encodedEntries) { - ser << opNames[op] << ' '; + ser << kOpNames[op] << ' '; } indexToSql(entry.LeftFieldName(), ser); ser << ' ' << entry.Condition() << ' '; diff --git a/cpp_src/core/query/sql/sqlparser.cc b/cpp_src/core/query/sql/sqlparser.cc index 172277c20..c2673c062 100644 --- a/cpp_src/core/query/sql/sqlparser.cc +++ b/cpp_src/core/query/sql/sqlparser.cc @@ -448,7 +448,6 @@ int SQLParser::deleteParse(tokenizer &parser) { } static void addUpdateValue(const token &currTok, tokenizer &parser, UpdateEntry &updateField) { - updateField.SetMode(FieldModeSet); if (currTok.type == TokenString) { updateField.Values().push_back(token2kv(currTok, parser, false)); } else { @@ -456,7 +455,7 @@ static void addUpdateValue(const token &currTok, tokenizer &parser, UpdateEntry updateField.Values().push_back(Variant()); } else if (currTok.text() == "{"sv) { try { - size_t jsonPos = parser.getPos() - 1; + size_t jsonPos = parser.getPrevPos(); std::string json(parser.begin() + jsonPos, parser.length() - jsonPos); size_t jsonLength = 0; gason::JsonParser jsonParser; @@ -520,6 +519,16 @@ void SQLParser::parseArray(tokenizer &parser, std::string_view tokText, UpdateEn throw Error(errParseSQL, "Expected ']' or ',', but found '%s' in query, %s", tok.text(), parser.where()); } } + + if (updateField && (updateField->Mode() == FieldModeSetJson)) { + for (const auto &it : updateField->Values()) { + if ((!it.Type().Is()) || + std::string_view(it).front() != '{') { + throw Error(errLogic, "Unexpected variant type in Array: %s. Expecting KeyValueType::String with JSON-content", + it.Type().Name()); + } + } + } } void SQLParser::parseCommand(tokenizer &parser) const { @@ -552,7 +561,7 @@ void SQLParser::parseCommand(tokenizer &parser) const { } // parse of possible concatenation - tok = parser.peek_token(false); + tok = parser.peek_token(tokenizer::flags::no_flags); while (tok.text() == "|"sv) { parser.next_token(); tok = parser.next_token(); @@ -587,6 +596,7 @@ UpdateEntry SQLParser::parseUpdateField(tokenizer &parser) { if (tok.text() == "["sv) { updateField.Values().MarkArray(); parseArray(parser, tok.text(), &updateField); + updateField.SetIsExpression(false); } else if (tok.text() == "array_remove"sv || tok.text() == "array_remove_once"sv) { parseCommand(parser); @@ -596,7 +606,7 @@ UpdateEntry SQLParser::parseUpdateField(tokenizer &parser) { addUpdateValue(tok, parser, updateField); } - tok = parser.peek_token(false); + tok = parser.peek_token(tokenizer::flags::no_flags); while (tok.text() == "|"sv) { parser.next_token(); tok = parser.next_token(); @@ -749,7 +759,7 @@ void SQLParser::parseWhereCondition(tokenizer &parser, T &&firstArg, OpType op) throw Error(errParseSQL, "Expected NULL, but found '%s' in query, %s", tok.text(), parser.where()); } query_.NextOp(op).Where(std::forward(firstArg), CondAny, VariantArray{}); - tok = parser.next_token(false); + tok = parser.next_token(tokenizer::flags::no_flags); } else if (tok.text() == "("sv) { if constexpr (!std::is_same_v) { if (iequals(peekSqlToken(parser, WhereFieldValueOrSubquerySqlToken, false).text(), "select"sv) && @@ -797,7 +807,7 @@ int SQLParser::parseWhere(tokenizer &parser) { int openBracketsCount = 0; while (!parser.end()) { tok = peekSqlToken(parser, nested == Nested::Yes ? NestedWhereFieldSqlToken : WhereFieldSqlToken, false); - parser.next_token(false); + parser.next_token(tokenizer::flags::no_flags); if (tok.text() == "("sv) { tok = peekSqlToken(parser, nested == Nested::Yes ? NestedWhereFieldSqlToken : WhereFieldOrSubquerySqlToken, false); if (nested == Nested::Yes || !iequals(tok.text(), "select"sv) || isCondition(parser.peek_second_token().text())) { diff --git a/cpp_src/core/queryresults/localqueryresults.cc b/cpp_src/core/queryresults/localqueryresults.cc index b71d1ee45..3add92dc7 100644 --- a/cpp_src/core/queryresults/localqueryresults.cc +++ b/cpp_src/core/queryresults/localqueryresults.cc @@ -89,7 +89,7 @@ std::string LocalQueryResults::Dump() const { if (&items_[i] != &*items_.begin()) buf += ","; buf += std::to_string(items_[i].Id()); if (joined_.empty()) continue; - Iterator itemIt{this, int(i), errOK}; + Iterator itemIt{this, int(i), Error(), {}}; auto joinIt = itemIt.GetJoined(); if (joinIt.getJoinedItemsCount() > 0) { buf += "["; @@ -133,11 +133,42 @@ int LocalQueryResults::GetJoinedNsCtxIndex(int nsid) const noexcept { class LocalQueryResults::EncoderDatasourceWithJoins final : public IEncoderDatasourceWithJoins { public: - EncoderDatasourceWithJoins(const joins::ItemIterator &joinedItemIt, const ContextsVector &ctxs, int ctxIdx) noexcept - : joinedItemIt_(joinedItemIt), ctxs_(ctxs), ctxId_(ctxIdx) {} + EncoderDatasourceWithJoins(const joins::ItemIterator &joinedItemIt, const ContextsVector &ctxs, Iterator::NsNamesCache &nsNamesCache, + int ctxIdx, size_t nsid, size_t joinedCount) noexcept + : joinedItemIt_(joinedItemIt), ctxs_(ctxs), nsNamesCache_(nsNamesCache), ctxId_(ctxIdx), nsid_{nsid} { + if (nsNamesCache.size() <= nsid_) { + nsNamesCache.resize(nsid_ + 1); + } + if (nsNamesCache[nsid_].size() < joinedCount) { + nsNamesCache[nsid_].clear(); + nsNamesCache[nsid_].reserve(joinedCount); + fast_hash_map namesCounters; + assertrx_dbg(ctxs_.size() >= ctxId_ + joinedCount); + for (size_t i = ctxId_, end = ctxId_ + joinedCount; i < end; ++i) { + const std::string &n = ctxs_[i].type_.Name(); + if (auto [it, emplaced] = namesCounters.emplace(n, -1); !emplaced) { + --it->second; + } + } + for (size_t i = ctxId_, end = ctxId_ + joinedCount; i < end; ++i) { + const std::string &n = ctxs_[i].type_.Name(); + int &count = namesCounters[n]; + if (count < 0) { + if (count == -1) { + nsNamesCache[nsid_].emplace_back(n); + } else { + count = 1; + nsNamesCache[nsid_].emplace_back("1_" + n); + } + } else { + nsNamesCache[nsid_].emplace_back(std::to_string(++count) + '_' + n); + } + } + } + } size_t GetJoinedRowsCount() const noexcept override { return joinedItemIt_.getJoinedFieldsCount(); } - size_t GetJoinedRowItemsCount(size_t rowId) const override { + size_t GetJoinedRowItemsCount(size_t rowId) const override final { auto fieldIt = joinedItemIt_.at(rowId); return fieldIt.ItemsCount(); } @@ -147,27 +178,25 @@ class LocalQueryResults::EncoderDatasourceWithJoins final : public IEncoderDatas const Context &ctx = ctxs_[ctxId_ + rowid]; return ConstPayload(ctx.type_, itemRef.Value()); } - const TagsMatcher &GetJoinedItemTagsMatcher(size_t rowid) override { + const TagsMatcher &GetJoinedItemTagsMatcher(size_t rowid) noexcept override { const Context &ctx = ctxs_[ctxId_ + rowid]; return ctx.tagsMatcher_; } - virtual const FieldsSet &GetJoinedItemFieldsFilter(size_t rowid) noexcept override { + const FieldsSet &GetJoinedItemFieldsFilter(size_t rowid) noexcept override { const Context &ctx = ctxs_[ctxId_ + rowid]; return ctx.fieldsFilter_; } - - const std::string &GetJoinedItemNamespace(size_t rowid) override { - const Context &ctx = ctxs_[ctxId_ + rowid]; - return ctx.type_->Name(); - } + const std::string &GetJoinedItemNamespace(size_t rowid) noexcept override { return nsNamesCache_[nsid_][rowid]; } private: const joins::ItemIterator &joinedItemIt_; const ContextsVector &ctxs_; + const Iterator::NsNamesCache &nsNamesCache_; const int ctxId_; + const size_t nsid_; }; -void LocalQueryResults::encodeJSON(int idx, WrSerializer &ser) const { +void LocalQueryResults::encodeJSON(int idx, WrSerializer &ser, Iterator::NsNamesCache &nsNamesCache) const { auto &itemRef = items_[idx]; assertrx(ctxs.size() > itemRef.Nsid()); auto &ctx = ctxs[itemRef.Nsid()]; @@ -182,7 +211,8 @@ void LocalQueryResults::encodeJSON(int idx, WrSerializer &ser) const { if (!joined_.empty()) { joins::ItemIterator itemIt = (begin() + idx).GetJoined(); if (itemIt.getJoinedItemsCount() > 0) { - EncoderDatasourceWithJoins joinsDs(itemIt, ctxs, GetJoinedNsCtxIndex(itemRef.Nsid())); + EncoderDatasourceWithJoins joinsDs(itemIt, ctxs, nsNamesCache, GetJoinedNsCtxIndex(itemRef.Nsid()), itemRef.Nsid(), + joined_[itemRef.Nsid()].GetJoinedSelectorsCount()); h_vector *, 2> dss; AdditionalDatasource ds = needOutputRank ? AdditionalDatasource(itemRef.Proc(), &joinsDs) : AdditionalDatasource(&joinsDs); dss.push_back(&ds); @@ -261,9 +291,9 @@ Error LocalQueryResults::Iterator::GetJSON(WrSerializer &ser, bool withHdrLen) { try { if (withHdrLen) { auto slicePosSaver = ser.StartSlice(); - qr_->encodeJSON(idx_, ser); + qr_->encodeJSON(idx_, ser, nsNamesCache); } else { - qr_->encodeJSON(idx_, ser); + qr_->encodeJSON(idx_, ser, nsNamesCache); } } catch (const Error &err) { err_ = err; @@ -287,9 +317,10 @@ CsvOrdering LocalQueryResults::MakeCSVTagOrdering(unsigned limit, unsigned offse fast_hash_set fieldsTmIds; WrSerializer ser; const auto &tm = getTagsMatcher(0); + Iterator::NsNamesCache nsNamesCache; for (size_t i = offset; i < items_.size() && i < offset + limit; ++i) { ser.Reset(); - encodeJSON(i, ser); + encodeJSON(i, ser, nsNamesCache); gason::JsonParser parser; auto jsonNode = parser.Parse(giftStr(ser.Slice())); @@ -321,7 +352,8 @@ CsvOrdering LocalQueryResults::MakeCSVTagOrdering(unsigned limit, unsigned offse if (!qr_->joined_.empty()) { joins::ItemIterator itemIt = (qr_->begin() + idx_).GetJoined(); if (itemIt.getJoinedItemsCount() > 0) { - EncoderDatasourceWithJoins joinsDs(itemIt, qr_->ctxs, qr_->GetJoinedNsCtxIndex(itemRef.Nsid())); + EncoderDatasourceWithJoins joinsDs(itemIt, qr_->ctxs, nsNamesCache, qr_->GetJoinedNsCtxIndex(itemRef.Nsid()), + itemRef.Nsid(), qr_->joined_[itemRef.Nsid()].GetJoinedSelectorsCount()); h_vector *, 2> dss; AdditionalDatasourceCSV ds(&joinsDs); dss.push_back(&ds); diff --git a/cpp_src/core/queryresults/localqueryresults.h b/cpp_src/core/queryresults/localqueryresults.h index e900242d5..863c17b99 100644 --- a/cpp_src/core/queryresults/localqueryresults.h +++ b/cpp_src/core/queryresults/localqueryresults.h @@ -100,11 +100,13 @@ class LocalQueryResults { const LocalQueryResults *qr_; int idx_; Error err_; + using NsNamesCache = h_vector, 1>; + NsNamesCache nsNamesCache; }; - Iterator begin() const noexcept { return Iterator{this, 0, Error()}; } - Iterator end() const noexcept { return Iterator{this, int(items_.size()), Error()}; } - Iterator operator[](int idx) const noexcept { return Iterator{this, idx, Error()}; } + Iterator begin() const noexcept { return Iterator{this, 0, Error(), {}}; } + Iterator end() const noexcept { return Iterator{this, int(items_.size()), Error(), {}}; } + Iterator operator[](int idx) const noexcept { return Iterator{this, idx, Error(), {}}; } std::vector joined_; std::vector aggregationResults; @@ -157,12 +159,11 @@ class LocalQueryResults { std::string explainResults; -protected: +private: class EncoderDatasourceWithJoins; class EncoderAdditionalDatasource; -private: - void encodeJSON(int idx, WrSerializer &ser) const; + void encodeJSON(int idx, WrSerializer &ser, Iterator::NsNamesCache &) const; ItemRefVector items_; std::vector rawDataHolder_; friend SelectFunctionsHolder; diff --git a/cpp_src/core/queryresults/queryresults.cc b/cpp_src/core/queryresults/queryresults.cc index baa6b7a36..56813f889 100644 --- a/cpp_src/core/queryresults/queryresults.cc +++ b/cpp_src/core/queryresults/queryresults.cc @@ -16,7 +16,7 @@ struct QueryResults::MergedData { std::string nsName; PayloadType pt; - TagsMatcher tm; + TagsMatcher tm; // Merged tagsmatcher currently does not have PayloadType and can not convert tags to indexes std::vector aggregationResults; bool haveRank = false; bool needOutputRank = false; @@ -77,7 +77,7 @@ QueryResults& QueryResults::operator=(QueryResults&& qr) noexcept { void QueryResults::AddQr(LocalQueryResults&& local, int shardID, bool buildMergedData) { if (local_) { - throw Error(errLogic, "Query results already have incapsulated local query results"); + throw Error(errLogic, "Query results already have encapsulated local query results"); } if (lastSeenIdx_ > 0) { throw Error( @@ -174,7 +174,6 @@ void QueryResults::RebuildMergedData() { const auto& agg = qrp.qr.GetAggregationResults(); if (mergedData_) { if (!iequals(mergedData_->pt.Name(), nss[0])) { - auto mrName = mergedData_->pt.Name(); throw Error(errLogic, "Query results in distributed query have different ns names: '%s' vs '%s'", mergedData_->pt.Name(), nss[0]); } @@ -240,7 +239,7 @@ void QueryResults::RebuildMergedData() { for (auto& qrp : remote_) { tmList.emplace_back(qrp.qr.GetTagsMatcher(0)); } - mergedData_->tm = TagsMatcher::CreateMergedTagsMatcher(mergedData_->pt, tmList); + mergedData_->tm = TagsMatcher::CreateMergedTagsMatcher(tmList); if (local_) { local_->hasCompatibleTm = local_->qr.getTagsMatcher(0).IsSubsetOf(mergedData_->tm); @@ -1199,7 +1198,7 @@ Error QueryResults::fillItemImpl(QrItT& it, ItemImpl& itemImpl, bool convertViaJ itemImpl.FromCJSON(wrser.Slice()); } else { err = it.GetJSON(wrser, false); - itemImpl.FromJSON(wrser.Slice()); + if (err.ok()) err = itemImpl.FromJSON(wrser.Slice()); } if (err.ok()) itemImpl.Value().SetLSN(it.GetLSN()); return err; diff --git a/cpp_src/core/queryresults/queryresults.h b/cpp_src/core/queryresults/queryresults.h index b2cccb9c4..2da09901a 100644 --- a/cpp_src/core/queryresults/queryresults.h +++ b/cpp_src/core/queryresults/queryresults.h @@ -302,7 +302,7 @@ class QueryResults { Error GetCJSON(WrSerializer &wrser, bool withHdrLen = true); Error GetMsgPack(WrSerializer &wrser, bool withHdrLen = true); Error GetProtobuf(WrSerializer &wrser, bool withHdrLen = true); - [[nodiscard]] Error GetCSV(WrSerializer &wrser, CsvOrdering &ordering) noexcept; + Error GetCSV(WrSerializer &wrser, CsvOrdering &ordering) noexcept; // use enableHold = false only if you are sure that the item will be destroyed before the LocalQueryResults Item GetItem(bool enableHold = true); diff --git a/cpp_src/core/reindexer.cc b/cpp_src/core/reindexer.cc index 116911832..914e9921d 100644 --- a/cpp_src/core/reindexer.cc +++ b/cpp_src/core/reindexer.cc @@ -27,7 +27,7 @@ static WrSerializer& printPkFields(const Item& item, WrSerializer& ser) { return ser; } -Reindexer::Reindexer(ReindexerConfig cfg) : impl_(new ShardingProxy(cfg)), owner_(true) { +Reindexer::Reindexer(ReindexerConfig cfg) : impl_(new ShardingProxy(std::move(cfg))), owner_(true) { // reindexer::CheckRequiredSSESupport(); } @@ -162,34 +162,30 @@ Error Reindexer::EnumMeta(std::string_view nsName, std::vector& key return impl_->EnumMeta(nsName, keys, rdxCtx); } Error Reindexer::DeleteMeta(std::string_view nsName, const std::string& key) { - const auto rdxCtx = impl_->CreateRdxContext( - ctx_, [&](WrSerializer& s) { s << "DELETE META FROM "sv << nsName << " WHERE KEY = '"sv << key << '\''; }); + const auto rdxCtx = + impl_->CreateRdxContext(ctx_, [&](WrSerializer& s) { s << "DELETE META FROM "sv << nsName << " WHERE KEY = '"sv << key << '\''; }); return impl_->DeleteMeta(nsName, key, rdxCtx); } Error Reindexer::Delete(const Query& q, QueryResults& result) { - const auto rdxCtx = impl_->CreateRdxContext( - ctx_, [&](WrSerializer& s) { q.GetSQL(s); }, result); + const auto rdxCtx = impl_->CreateRdxContext(ctx_, [&](WrSerializer& s) { q.GetSQL(s); }, result); return impl_->Delete(q, result, rdxCtx); } Error Reindexer::Select(std::string_view query, QueryResults& result, unsigned proxyFetchLimit) { - const auto rdxCtx = impl_->CreateRdxContext( - ctx_, [&](WrSerializer& s) { s << query; }, result); + const auto rdxCtx = impl_->CreateRdxContext(ctx_, [&](WrSerializer& s) { s << query; }, result); return impl_->Select(query, result, proxyFetchLimit, rdxCtx); } Error Reindexer::Select(const Query& q, QueryResults& result, unsigned proxyFetchLimit) { - const auto rdxCtx = impl_->CreateRdxContext( - ctx_, [&](WrSerializer& s) { q.GetSQL(s); }, result); + const auto rdxCtx = impl_->CreateRdxContext(ctx_, [&](WrSerializer& s) { q.GetSQL(s); }, result); return impl_->Select(q, result, proxyFetchLimit, rdxCtx); } +Error Reindexer::Commit(std::string_view) { + // Empty + return {}; +} Error Reindexer::Update(const Query& q, QueryResults& result) { - const auto rdxCtx = impl_->CreateRdxContext( - ctx_, [&](WrSerializer& s) { q.GetSQL(s); }, result); + const auto rdxCtx = impl_->CreateRdxContext(ctx_, [&](WrSerializer& s) { q.GetSQL(s); }, result); return impl_->Update(q, result, rdxCtx); } -Error Reindexer::Commit(std::string_view nsName) { - const auto rdxCtx = impl_->CreateRdxContext(ctx_, [&](WrSerializer& s) { s << "COMMIT TRANSACTION "sv << nsName; }); - return impl_->Commit(nsName, rdxCtx); -} Error Reindexer::AddIndex(std::string_view nsName, const IndexDef& idx) { const auto rdxCtx = impl_->CreateRdxContext(ctx_, [&](WrSerializer& s) { s << "CREATE INDEX "sv << idx.name_ << " ON "sv << nsName; }); return impl_->AddIndex(nsName, idx, rdxCtx); @@ -262,15 +258,20 @@ Error Reindexer::DumpIndex(std::ostream& os, std::string_view nsName, std::strin return impl_->DumpIndex(os, nsName, index, rdxCtx); } +Error Reindexer::SubscribeUpdates(IEventsObserver& observer, EventSubscriberConfig&& cfg) { + return impl_->SubscribeUpdates(observer, std::move(cfg)); +} +Error Reindexer::UnsubscribeUpdates(IEventsObserver& observer) { return impl_->UnsubscribeUpdates(observer); } + [[nodiscard]] Error Reindexer::ShardingControlRequest(const sharding::ShardingControlRequestData& request) noexcept { return impl_->ShardingControlRequest(request, impl_->CreateRdxContext(ctx_, [&](WrSerializer& s) { s << "SHARDING CONTROL REQUEST"; })); } // REINDEX_WITH_V3_FOLLOWERS -Error Reindexer::SubscribeUpdates(IUpdatesObserver* observer, const UpdatesFilters& filters, SubscriptionOpts opts) { +Error Reindexer::SubscribeUpdates(IUpdatesObserverV3* observer, const UpdatesFilters& filters, SubscriptionOpts opts) { return impl_->SubscribeUpdates(observer, filters, opts); } -Error Reindexer::UnsubscribeUpdates(IUpdatesObserver* observer) { return impl_->UnsubscribeUpdates(observer); } +Error Reindexer::UnsubscribeUpdates(IUpdatesObserverV3* observer) { return impl_->UnsubscribeUpdates(observer); } // REINDEX_WITH_V3_FOLLOWERS } // namespace reindexer diff --git a/cpp_src/core/reindexer.h b/cpp_src/core/reindexer.h index bf9b7a27c..c10b9d958 100644 --- a/cpp_src/core/reindexer.h +++ b/cpp_src/core/reindexer.h @@ -21,9 +21,11 @@ class SnapshotChunk; class Snapshot; struct SnapshotOpts; struct ClusterControlRequestData; +class IEventsObserver; // TODO: Make this class accessible to the external user #1714 +class EventSubscriberConfig; // TODO: Make this class accessible to the external user #1714 // REINDEX_WITH_V3_FOLLOWERS -class IUpdatesObserver; +class IUpdatesObserverV3; class UpdatesFilters; namespace cluster { @@ -197,9 +199,8 @@ class Reindexer { /// @param result - QueryResults with found items /// @param proxyFetchLimit - Fetch limit for proxied query Error Select(const Query &query, QueryResults &result, unsigned proxyFetchLimit = 10000); - /// Flush changes to storage - /// Cancellation context doesn't affect this call - /// @param nsName - Name of namespace + /// *DEPRECATED* This method does nothing + /// TODO: Must be removed after python-binding update #1800 Error Commit(std::string_view nsName); /// Allocate new item for namespace /// @param nsName - Name of namespace @@ -350,8 +351,8 @@ class Reindexer { /// @param connectionId - unique identifier for the connection Reindexer WithContextParams(milliseconds timeout, lsn_t lsn, int emitterServerId, int shardId, bool distributed, std::string_view activityTracer, std::string user, int connectionId) const { - return {impl_, ctx_.WithContextParams(timeout, lsn, emitterServerId, shardId, distributed, activityTracer, std::move(user), - connectionId)}; + return {impl_, + ctx_.WithContextParams(timeout, lsn, emitterServerId, shardId, distributed, activityTracer, std::move(user), connectionId)}; } /// Allows to set multiple context params at once /// @param timeout - Execution timeout @@ -381,18 +382,30 @@ class Reindexer { Error DumpIndex(std::ostream &os, std::string_view nsName, std::string_view index); + /// Subscribe to updates of database + /// Cancelation context doesn't affect this call + /// @param observer - Observer interface, which will receive updates + /// @param cfg - Subscription config + /// Each subscriber may have multiple event streams. Reindexer will create streamsMask for each event to route it to the proper stream. + Error SubscribeUpdates(IEventsObserver &observer, EventSubscriberConfig &&cfg); + /// Unsubscribe from updates of database + /// Cancelation context doesn't affect this call + /// @param observer - Observer interface, which will be unsubscribed updates + Error UnsubscribeUpdates(IEventsObserver &observer); + + /// ***Deprecated*** V3 methods /// REINDEX_WITH_V3_FOLLOWERS /// THIS METHOD IS TEMPORARY AND WILL BE REMOVED /// Subscribe to updates of database /// @param observer - Observer interface, which will receive updates /// @param filters - Subscription filters set /// @param opts - Subscription options (allows to either add new filters or reset them) - Error SubscribeUpdates(IUpdatesObserver *observer, const UpdatesFilters &filters, SubscriptionOpts opts = SubscriptionOpts()); + Error SubscribeUpdates(IUpdatesObserverV3 *observer, const UpdatesFilters &filters, SubscriptionOpts opts = SubscriptionOpts()); /// THIS METHOD IS TEMPORARY AND WILL BE REMOVED /// Unsubscribe from updates of database /// Cancellation context doesn't affect this call /// @param observer - Observer interface, which will be unsubscribed updates - Error UnsubscribeUpdates(IUpdatesObserver *observer); + Error UnsubscribeUpdates(IUpdatesObserverV3 *observer); private: Reindexer(ShardingProxy *impl, InternalRdxContext &&ctx) noexcept : impl_(impl), owner_(false), ctx_(std::move(ctx)) {} diff --git a/cpp_src/core/reindexer_impl/reindexerimpl.cc b/cpp_src/core/reindexer_impl/reindexerimpl.cc index 6250fc407..144c697c8 100644 --- a/cpp_src/core/reindexer_impl/reindexerimpl.cc +++ b/cpp_src/core/reindexer_impl/reindexerimpl.cc @@ -11,14 +11,11 @@ #include "core/iclientsstats.h" #include "core/index/index.h" #include "core/itemimpl.h" -#include "core/nsselecter/nsselecter.h" #include "core/nsselecter/querypreprocessor.h" #include "core/query/sql/sqlsuggester.h" #include "core/queryresults/joinresults.h" #include "core/selectfunc/selectfunc.h" -#include "core/type_consts_helpers.h" #include "debug/crashqueryreporter.h" -#include "estl/defines.h" #include "rx_selector.h" #include "server/outputparameters.h" #include "tools/alloc_ext/tc_malloc_extension.h" @@ -34,7 +31,7 @@ static std::once_flag initTerminateHandlerFlag; using namespace std::placeholders; -using reindexer::cluster::UpdateRecord; +using reindexer::updates::UpdateRecord; using namespace std::string_view_literals; namespace reindexer { @@ -70,14 +67,20 @@ static unsigned ConcurrentNamespaceLoaders() noexcept { } ReindexerImpl::ReindexerImpl(ReindexerConfig cfg, ActivityContainer& activities, CallbackMap&& proxyCallbacks) - : dbDestroyed_{false}, - clusterizator_(new cluster::Clusterizator(*this, cfg.maxReplUpdatesSize)), + : clusterizator_(std::make_unique(*this, cfg.maxReplUpdatesSize)), nsLock_(*clusterizator_, *this), activities_(activities), storageType_(StorageType::LevelDB), config_(std::move(cfg)), - proxyCallbacks_(std::move(proxyCallbacks)) { + proxyCallbacks_(std::move(proxyCallbacks)), + observers_(config_.dbName, *clusterizator_, config_.maxReplUpdatesSize) { configProvider_.setHandler(ProfilingConf, std::bind(&ReindexerImpl::onProfiligConfigLoad, this)); + replCfgHandlerID_ = + configProvider_.setHandler([this](const ReplicationConfigData& cfg) noexcept { observers_.SetEventsServerID(cfg.serverID); }); + shardingConfig_.setHandled([this](const ShardinConfigPtr& cfg) noexcept { + observers_.SetEventsShardID(cfg ? cfg->thisShardId : ShardingKeyType::NotSetShard); + }); + configWatchers_.emplace_back( kReplicationConfFilename, [this](const std::string& yaml) { return tryLoadConfFromYAML(yaml); }, @@ -104,6 +107,9 @@ ReindexerImpl::ReindexerImpl(ReindexerConfig cfg, ActivityContainer& activities, } ReindexerImpl::~ReindexerImpl() { + if (replCfgHandlerID_.has_value()) { + configProvider_.unsetHandler(*replCfgHandlerID_); + } for (auto& ns : namespaces_) { // Add extra checks to avoid GCC 13 warnings in Release build. Actually namespaces are never null if (ns.second) { @@ -378,13 +384,8 @@ Error ReindexerImpl::AddNamespace(const NamespaceDef& nsDef, std::optional(nsDef.name, replOpts.has_value() ? replOpts->tmStateToken : std::optional(), *clusterizator_, bgDeleter_, observers_); -#else // REINDEX_WITH_V3_FOLLOWERS - ns = std::make_shared(nsDef.name, replOpts.has_value() ? replOpts->tmStateToken : std::optional(), - *clusterizator_, bgDeleter_); -#endif // REINDEX_WITH_V3_FOLLOWERS if (nsDef.isTemporary) { ns->awaitMainNs(rdxCtx)->setTemporary(); } @@ -398,8 +399,11 @@ Error ReindexerImpl::AddNamespace(const NamespaceDef& nsDef, std::optionalSetClusterizationStatus(ClusterizationStatus{rdxCtx.GetOriginLSN().Server(), ClusterizationStatus::Role::ClusterReplica}, - rdxCtx); + auto err = ns->SetClusterizationStatus( + ClusterizationStatus{rdxCtx.GetOriginLSN().Server(), ClusterizationStatus::Role::ClusterReplica}, rdxCtx); + if (!err.ok()) { + return err; + } } rdxCtx.WithNoWaitSync(nsDef.isTemporary || ns->IsSystem(rdxCtx) || !clusterizator_->NamespaceIsInClusterConfig(nsDef.name)); const int64_t stateToken = ns->NewItem(rdxCtx).GetStateToken(); @@ -408,13 +412,12 @@ Error ReindexerImpl::AddNamespace(const NamespaceDef& nsDef, std::optionalReplicate( - {UpdateRecord::Type::AddNamespace, nsDef.name, version, rdxCtx.EmmiterServerId(), std::move(def), stateToken}, + auto err = observers_.SendUpdate( + {updates::URType::AddNamespace, it.value()->GetName(RdxContext()), version, rdxCtx.EmmiterServerId(), std::move(def), + stateToken}, [&wlck] { assertrx(wlck.isClusterLck()); wlck.unlock(); }, rdxCtx); if (!err.ok()) { - throw err; + return err; } } } @@ -528,8 +532,8 @@ Error ReindexerImpl::closeNamespace(std::string_view nsName, const RdxContext& c observers_.OnWALUpdate(LSNPair(), nsName, WALRecord(WalNamespaceDrop)); } #endif // REINDEX_WITH_V3_FOLLOWERS - auto err = clusterizator_->Replicate( - {dropStorage ? UpdateRecord::Type::DropNamespace : UpdateRecord::Type::CloseNamespace, std::string(nsName), lsn_t(0, 0), + auto err = observers_.SendUpdate( + {dropStorage ? updates::URType::DropNamespace : updates::URType::CloseNamespace, ns->GetName(RdxContext()), lsn_t(0, 0), lsn_t(0, 0), ctx.EmmiterServerId()}, [&wlck] { assertrx(wlck.isClusterLck()); @@ -558,11 +562,11 @@ Error ReindexerImpl::openNamespace(std::string_view name, bool skipNameCheck, co if (nsIt != namespaces_.end() && nsIt->second) { rlck.unlock(); if (rdxCtx.HasEmmiterServer()) { - return clusterizator_->Replicate( - UpdateRecord{UpdateRecord::Type::EmptyUpdate, std::string(name), rdxCtx.EmmiterServerId()}, std::function(), - rdxCtx); + return observers_.SendUpdate( + UpdateRecord{updates::URType::EmptyUpdate, nsIt.value()->GetName(rdxCtx), rdxCtx.EmmiterServerId()}, + std::function(), rdxCtx); } - return errOK; + return Error(); } } if (!skipNameCheck && !validateUserNsName(name)) { @@ -570,13 +574,8 @@ Error ReindexerImpl::openNamespace(std::string_view name, bool skipNameCheck, co } NamespaceDef nsDef(std::string(name), storageOpts); assertrx(clusterizator_); -#ifdef REINDEX_WITH_V3_FOLLOWERS auto ns = std::make_shared(nsDef.name, replOpts.has_value() ? replOpts->tmStateToken : std::optional(), *clusterizator_, bgDeleter_, observers_); -#else // REINDEX_WITH_V3_FOLLOWERS - auto ns = std::make_shared(nsDef.name, replOpts.has_value() ? replOpts->tmStateToken : std::optional(), - *clusterizator_, bgDeleter_); -#endif // REINDEX_WITH_V3_FOLLOWERS if (storageOpts.IsEnabled() && !storagePath_.empty()) { auto opts = storageOpts; ns->EnableStorage(storagePath_, opts.Autorepair(autorepairEnabled_), storageType_, rdxCtx); @@ -589,7 +588,10 @@ Error ReindexerImpl::openNamespace(std::string_view name, bool skipNameCheck, co ClusterizationStatus clStatus; clStatus.role = ClusterizationStatus::Role::ClusterReplica; // TODO: It may be a simple replica clStatus.leaderId = rdxCtx.GetOriginLSN().Server(); - ns->SetClusterizationStatus(std::move(clStatus), rdxCtx); + auto err = ns->SetClusterizationStatus(std::move(clStatus), rdxCtx); + if (!err.ok()) { + return err; + } } rdxCtx.WithNoWaitSync(ns->IsSystem(rdxCtx) || !clusterizator_->NamespaceIsInClusterConfig(nsDef.name)); const int64_t stateToken = ns->NewItem(rdxCtx).GetStateToken(); @@ -600,14 +602,16 @@ Error ReindexerImpl::openNamespace(std::string_view name, bool skipNameCheck, co const lsn_t version = setNsVersion(ns, replOpts, rdxCtx); - namespaces_.insert({nsDef.name, std::move(ns)}); + auto [nsIt, inserted] = namespaces_.insert({nsDef.name, std::move(ns)}); + (void)inserted; #ifdef REINDEX_WITH_V3_FOLLOWERS if (!nsDef.isTemporary) { observers_.OnWALUpdate(LSNPair(), nsDef.name, WALRecord(WalNamespaceAdd)); } #endif // REINDEX_WITH_V3_FOLLOWERS - auto err = clusterizator_->Replicate( - {UpdateRecord::Type::AddNamespace, std::string(name), version, rdxCtx.EmmiterServerId(), std::move(nsDef), stateToken}, + auto err = observers_.SendUpdate( + {updates::URType::AddNamespace, nsIt.value()->GetName(RdxContext()), version, rdxCtx.EmmiterServerId(), std::move(nsDef), + stateToken}, [&wlck] { assertrx(wlck.isClusterLck()); wlck.unlock(); @@ -669,18 +673,25 @@ bool ReindexerImpl::NamespaceIsInClusterConfig(std::string_view nsName) { return clusterizator_ && clusterizator_->NamespaceIsInClusterConfig(nsName); } -Error ReindexerImpl::SubscribeUpdates([[maybe_unused]] IUpdatesObserver* observer, [[maybe_unused]] const UpdatesFilters& filters, +Error ReindexerImpl::SubscribeUpdates(IEventsObserver& observer, EventSubscriberConfig&& cfg) { + return observers_.AddOrUpdate(observer, std::move(cfg)); +} + +Error ReindexerImpl::UnsubscribeUpdates(IEventsObserver& observer) { return observers_.Remove(observer); } + +Error ReindexerImpl::SubscribeUpdates([[maybe_unused]] IUpdatesObserverV3* observer, [[maybe_unused]] const UpdatesFilters& filters, [[maybe_unused]] SubscriptionOpts opts) { #ifdef REINDEX_WITH_V3_FOLLOWERS - return observers_.Add(observer, filters, opts); + observers_.Add(observer, filters, opts); + return {}; #else // REINDEX_WITH_V3_FOLLOWERS return Error(errForbidden, "Reindexer was built without v3 followers compatibility"); #endif // REINDEX_WITH_V3_FOLLOWERS } -Error ReindexerImpl::UnsubscribeUpdates([[maybe_unused]] IUpdatesObserver* observer) { +Error ReindexerImpl::UnsubscribeUpdates([[maybe_unused]] IUpdatesObserverV3* observer) { #ifdef REINDEX_WITH_V3_FOLLOWERS - return observers_.Delete(observer); + return observers_.Remove(observer); #else // REINDEX_WITH_V3_FOLLOWERS return Error(errForbidden, "Reindexer was built without v3 followers compatibility"); #endif // REINDEX_WITH_V3_FOLLOWERS @@ -749,7 +760,7 @@ Error ReindexerImpl::renameNamespace(std::string_view srcNsName, const std::stri namespaces_[dstNsName] = srcNs; std::function)> replicateCb; if (needWalUpdate) { - replicateCb = [/*this, &srcNsName, &dstNsName, &rdxCtx, &wlck*/](const std::function& /*unlockCb*/) { + replicateCb = [this, &srcNsName, &dstNsName, &rdxCtx /*, &wlck*/](const std::function& /*unlockCb*/) { // TODO: Implement rename replication // auto err = clusterizator_->Replicate( // {UpdateRecord::Type::RenameNamespace, std::string(srcNsName), lsn_t(0, 0), @@ -763,11 +774,16 @@ Error ReindexerImpl::renameNamespace(std::string_view srcNsName, const std::stri // if (!err.ok()) { // throw err; // } + + // Temporary solution. Do not replicating rename command, but still sending rename event + observers_.SendAsyncEventOnly({updates::URType::RenameNamespace, NamespaceName(srcNsName), lsn_t(0, 0), lsn_t(0, 0), + rdxCtx.EmmiterServerId(), dstNsName}); }; } else if (!skipResync) { replicateCb = [this, &dstNsName, &rdxCtx](const std::function&) { - auto err = clusterizator_->ReplicateAsync( - {UpdateRecord::Type::ResyncNamespaceGeneric, dstNsName, lsn_t(0, 0), lsn_t(0, 0), rdxCtx.EmmiterServerId()}, rdxCtx); + auto err = observers_.SendAsyncUpdate( + {updates::URType::ResyncNamespaceGeneric, NamespaceName(dstNsName), lsn_t(0, 0), lsn_t(0, 0), rdxCtx.EmmiterServerId()}, + rdxCtx); if (!err.ok()) { throw err; } @@ -791,10 +807,10 @@ Error ReindexerImpl::renameNamespace(std::string_view srcNsName, const std::stri } #endif // REINDEX_WITH_V3_FOLLOWERS } catch (...) { - auto actualName = srcNs->GetName(rdxCtx); - if (actualName != dstNsName) { + auto actualName = srcNs->GetName(RdxContext()); + if (std::string_view(actualName) != toLower(dstNsName)) { namespaces_.erase(dstNsName); - namespaces_[actualName] = srcNs; + namespaces_[std::string(actualName)] = srcNs; } throw; } @@ -952,9 +968,10 @@ Error ReindexerImpl::Update(const Query& q, LocalQueryResults& result, const Rdx auto ns = getNamespace(q.NsName(), rdxCtx); ns->Update(q, result, rdxCtx); if (ns->IsSystem(rdxCtx)) { - const std::string kNsName = ns->GetName(rdxCtx); + const auto kNsNamePtr = ns->GetName(rdxCtx); + const std::string_view kNsName(kNsNamePtr); rdxCtx.WithNoWaitSync(true); - for (auto it = result.begin(); it != result.end(); ++it) { + for (auto& it : result) { auto item = it.GetItem(false); updateToSystemNamespace(kNsName, item, rdxCtx); } @@ -962,7 +979,7 @@ Error ReindexerImpl::Update(const Query& q, LocalQueryResults& result, const Rdx } catch (const Error& err) { return err; } - return errOK; + return {}; } Error ReindexerImpl::Upsert(std::string_view nsName, Item& item, const RdxContext& ctx) { APPLY_NS_FUNCTION1(true, Upsert, item); } @@ -1062,13 +1079,12 @@ Error ReindexerImpl::Select(const Query& q, LocalQueryResults& result, const Rdx const QueriesStatTracer::QuerySQL sql{normalizedSQL.Slice(), nonNormalizedSQL.Slice()}; auto hitter = queriesPerfStatsEnabled - ? [&sql, &tracker](bool lockHit, std::chrono::microseconds time) { - if (lockHit) - tracker.LockHit(sql, time); - else - tracker.Hit(sql, time); - } - : std::function{}; + ? [&sql, &tracker](bool lockHit, std::chrono::microseconds time) { + if (lockHit) + tracker.LockHit(sql, time); + else + tracker.Hit(sql, time); + } : std::function{}; const bool isSystemNsRequest = isSystemNamespaceNameFast(q.NsName()); QueryStatCalculator statCalculator( @@ -1083,12 +1099,16 @@ Error ReindexerImpl::Select(const Query& q, LocalQueryResults& result, const Rdx } // Lookup and lock namespaces_ mainNs->updateSelectTime(); - locks.Add(mainNs); - q.WalkNested(false, true, true, [this, &locks, &rdxCtx](const Query& q) { - auto nsWrp = getNamespace(q.NsName(), rdxCtx); - auto ns = q.IsWALQuery() ? nsWrp->awaitMainNs(rdxCtx) : nsWrp->getMainNs(); + locks.Add(std::move(mainNs)); + struct { + RxSelector::NsLocker& locks; + const RdxContext& ctx; + } refs{locks, rdxCtx}; + q.WalkNested(false, true, true, [this, &refs](const Query& q) { + auto nsWrp = getNamespace(q.NsName(), refs.ctx); + auto ns = q.IsWALQuery() ? nsWrp->awaitMainNs(refs.ctx) : nsWrp->getMainNs(); ns->updateSelectTime(); - locks.Add(ns); + refs.locks.Add(std::move(ns)); }); locks.Lock(); @@ -1112,17 +1132,6 @@ Error ReindexerImpl::Select(const Query& q, LocalQueryResults& result, const Rdx return Error(); } -Error ReindexerImpl::Commit(std::string_view /*_namespace*/) { - try { - // getNamespace(_namespace)->FlushStorage(); - - } catch (const Error& err) { - return err; - } - - return errOK; -} - std::set ReindexerImpl::getFTIndexes(std::string_view nsName) { const RdxContext rdxCtx; auto rlck = nsLock_.RLock(rdxCtx); @@ -1258,11 +1267,7 @@ Error ReindexerImpl::EnumNamespaces(std::vector& defs, EnumNamespa if (namespaces_.find(d.name) != namespaces_.end()) continue; } assertrx(clusterizator_); -#ifdef REINDEX_WITH_V3_FOLLOWERS - std::unique_ptr tmpNs{new NamespaceImpl(d.name, {}, *clusterizator_, observers_)}; -#else // REINDEX_WITH_V3_FOLLOWERS - std::unique_ptr tmpNs{new NamespaceImpl(d.name, {}, *clusterizator_)}; -#endif // REINDEX_WITH_V3_FOLLOWERS + auto tmpNs = std::make_unique(d.name, std::optional(), *clusterizator_, observers_); try { tmpNs->EnableStorage(storagePath_, StorageOpts(), storageType_, rdxCtx); if (opts.IsHideTemporary() && tmpNs->IsTemporary(rdxCtx)) { @@ -1396,11 +1401,14 @@ void ReindexerImpl::storageFlushingRoutine(net::ev::dynamic_loop& loop) { void ReindexerImpl::createSystemNamespaces() { for (const auto& nsDef : kSystemNsDefs) { - AddNamespace(nsDef); + auto err = AddNamespace(nsDef); + if (!err.ok()) { + logPrintf(LogWarning, "Unable to create system namespace '%s': %s", nsDef.name, err.what()); + } } } -[[nodiscard]] Error ReindexerImpl::tryLoadShardingConf(const RdxContext& ctx) noexcept { +Error ReindexerImpl::tryLoadShardingConf(const RdxContext& ctx) noexcept { try { Item item = NewItem(kConfigNamespace, ctx); if (!item.Status().ok()) return item.Status(); @@ -1568,7 +1576,7 @@ Error ReindexerImpl::tryLoadConfFromYAML(const std::string& yamlConf) { if (!item.Status().ok()) { return item.Status(); } - err = item.FromJSON(ser.Slice()); + err = item.Unsafe().FromJSON(ser.Slice()); if (!err.ok()) { return err; } @@ -1671,7 +1679,10 @@ void ReindexerImpl::handleConfigAction(const gason::JsonNode& action, const std: if (name.empty()) { for (auto& ns : namespaces) { if (!clusterizator_->NamespaceIsInClusterConfig(ns.first)) { - ns.second->SetClusterizationStatus(ClusterizationStatus(), ctx); + auto err = ns.second->SetClusterizationStatus(ClusterizationStatus(), ctx); + if (!err.ok()) { + throw err; + } } } } else { @@ -1680,7 +1691,10 @@ void ReindexerImpl::handleConfigAction(const gason::JsonNode& action, const std: } auto ns = getNamespaceNoThrow(name, ctx); if (ns) { - ns->SetClusterizationStatus(ClusterizationStatus(), ctx); + auto err = ns->SetClusterizationStatus(ClusterizationStatus(), ctx); + if (!err.ok()) { + throw err; + } } } } else if (command == "set_log_level"sv) { @@ -1813,7 +1827,9 @@ ReindexerImpl::FilterNsNamesT ReindexerImpl::detectFilterNsNames(const Query& q) const auto activityCtx = ctx.OnlyActivity(); for (auto& nspair : nsarray) { if (filterNsNames.has_value()) { - if (std::find(filterNsNames->cbegin(), filterNsNames->cend(), nspair.first) == filterNsNames->cend()) { + if (std::find_if(filterNsNames->cbegin(), filterNsNames->cend(), [&nspair](std::string_view name) noexcept { + return iequals(nspair.first, name); + }) == filterNsNames->cend()) { continue; } } @@ -1839,7 +1855,7 @@ ReindexerImpl::FilterNsNamesT ReindexerImpl::detectFilterNsNames(const Query& q) forEachNS(getNamespace(kPerfStatsNamespace, ctx), false, [&ctx](std::string_view nsName, const Namespace::Ptr& nsPtr, WrSerializer& ser) { auto stats = nsPtr->GetPerfStat(ctx); - bool notRenamed = (stats.name == nsName); + bool notRenamed = iequals(stats.name, nsName); if (notRenamed) stats.GetJSON(ser); return notRenamed; }); @@ -1849,7 +1865,7 @@ ReindexerImpl::FilterNsNamesT ReindexerImpl::detectFilterNsNames(const Query& q) forEachNS(getNamespace(kMemStatsNamespace, ctx), false, [&ctx](std::string_view nsName, const Namespace::Ptr& nsPtr, WrSerializer& ser) { auto stats = nsPtr->GetMemStat(ctx); - bool notRenamed = (stats.name == nsName); + bool notRenamed = iequals(stats.name, nsName); if (notRenamed) stats.GetJSON(ser); return notRenamed; }); @@ -1858,7 +1874,7 @@ ReindexerImpl::FilterNsNamesT ReindexerImpl::detectFilterNsNames(const Query& q) forEachNS(getNamespace(kNamespacesNamespace, ctx), true, [&ctx](std::string_view nsName, const Namespace::Ptr& nsPtr, WrSerializer& ser) { auto stats = nsPtr->GetDefinition(ctx); - bool notRenamed = (stats.name == nsName); + bool notRenamed = iequals(stats.name, nsName); if (notRenamed) stats.GetJSON(ser, kIndexJSONWithDescribe); return notRenamed; }); @@ -1945,9 +1961,11 @@ ReindexerImpl::FilterNsNamesT ReindexerImpl::detectFilterNsNames(const Query& q) void ReindexerImpl::onProfiligConfigLoad() { LocalQueryResults qr1, qr2, qr3; - Delete(Query(kMemStatsNamespace), qr2, RdxContext()); - Delete(Query(kQueriesPerfStatsNamespace), qr3, RdxContext()); - Delete(Query(kPerfStatsNamespace), qr1, RdxContext()); + RdxContext ctx; + auto err = Delete(Query(kMemStatsNamespace), qr2, ctx); + err = Delete(Query(kQueriesPerfStatsNamespace), qr3, ctx); + err = Delete(Query(kPerfStatsNamespace), qr1, ctx); + (void)err; // ignore } Error ReindexerImpl::GetSqlSuggestions(std::string_view sqlQuery, int pos, std::vector& suggestions, @@ -1957,7 +1975,8 @@ Error ReindexerImpl::GetSqlSuggestions(std::string_view sqlQuery, int pos, std:: suggestions = SQLSuggester::GetSuggestions( sqlQuery, pos, [&, this](EnumNamespacesOpts opts) { - EnumNamespaces(nses, opts, rdxCtx); + auto err = EnumNamespaces(nses, opts, rdxCtx); + (void)err; // ignore return nses; }, [&rdxCtx, this](std::string_view ns) { @@ -2069,7 +2088,7 @@ Error ReindexerImpl::GetProtobufSchema(WrSerializer& ser, std::vectorGetReplStateV2(rdxCtx); @@ -2079,37 +2098,34 @@ Error ReindexerImpl::GetReplState(std::string_view nsName, ReplicationStateV2& s auto rlck = nsLock_.RLock(rdxCtx); state.clusterStatus = clusterStatus_; } - } catch (const Error& err) { - return err; } - return errOK; + CATCH_AND_RETURN; + return {}; } -Error ReindexerImpl::SetClusterizationStatus(std::string_view nsName, const ClusterizationStatus& status, const RdxContext& rdxCtx) { +Error ReindexerImpl::SetClusterizationStatus(std::string_view nsName, const ClusterizationStatus& status, + const RdxContext& rdxCtx) noexcept { try { return getNamespace(nsName, rdxCtx)->SetClusterizationStatus(ClusterizationStatus(status), rdxCtx); - } catch (const Error& err) { - return err; } - return errOK; + CATCH_AND_RETURN; + return {}; } -Error ReindexerImpl::GetSnapshot(std::string_view nsName, const SnapshotOpts& opts, Snapshot& snapshot, const RdxContext& rdxCtx) { +Error ReindexerImpl::GetSnapshot(std::string_view nsName, const SnapshotOpts& opts, Snapshot& snapshot, const RdxContext& rdxCtx) noexcept { try { getNamespace(nsName, rdxCtx)->GetSnapshot(snapshot, opts, rdxCtx); - } catch (const Error& err) { - return err; } - return errOK; + CATCH_AND_RETURN; + return {}; } -Error ReindexerImpl::ApplySnapshotChunk(std::string_view nsName, const SnapshotChunk& ch, const RdxContext& rdxCtx) { +Error ReindexerImpl::ApplySnapshotChunk(std::string_view nsName, const SnapshotChunk& ch, const RdxContext& rdxCtx) noexcept { try { getNamespace(nsName, rdxCtx)->ApplySnapshotChunk(ch, false, rdxCtx); - } catch (const Error& err) { - return err; } - return errOK; + CATCH_AND_RETURN; + return {}; } bool ReindexerImpl::isSystemNamespaceNameStrict(std::string_view name) noexcept { @@ -2176,7 +2192,7 @@ Error ReindexerImpl::shardingConfigReplAction(const RdxContext& ctx, PreReplFunc return Error(); } - return clusterizator_->Replicate( + return observers_.SendUpdate( makeUpdateRecord(func(std::forward(args)...), std::make_index_sequence>{}), [&wlck] { assertrx(wlck.isClusterLck()); @@ -2188,7 +2204,7 @@ Error ReindexerImpl::shardingConfigReplAction(const RdxContext& ctx, PreReplFunc } template -Error ReindexerImpl::shardingConfigReplAction(const RdxContext& ctx, cluster::UpdateRecord::Type type, Args&&... args) noexcept { +Error ReindexerImpl::shardingConfigReplAction(const RdxContext& ctx, updates::URType type, Args&&... args) noexcept { return shardingConfigReplAction( ctx, [&ctx, &type](Args&&... aargs) { return std::make_tuple(type, ctx.EmmiterServerId(), std::forward(aargs)...); }, std::forward(args)...); @@ -2213,26 +2229,26 @@ Error ReindexerImpl::saveShardingCfgCandidate(std::string_view config, int64_t s } } - return std::make_tuple(UpdateRecord::Type::SaveShardingConfig, ctx.EmmiterServerId(), std::string(config), sourceId); + return std::make_tuple(updates::URType::SaveShardingConfig, ctx.EmmiterServerId(), std::string(config), sourceId); }; return shardingConfigReplAction(ctx, std::move(preReplfunc), config, sourceId); } Error ReindexerImpl::applyShardingCfgCandidate(int64_t sourceId, const RdxContext& ctx) noexcept { - return shardingConfigReplAction(ctx, UpdateRecord::Type::ApplyShardingConfig, sourceId); + return shardingConfigReplAction(ctx, updates::URType::ApplyShardingConfig, sourceId); } Error ReindexerImpl::resetOldShardingConfig(int64_t sourceId, const RdxContext& ctx) noexcept { - return shardingConfigReplAction(ctx, UpdateRecord::Type::ResetOldShardingConfig, sourceId); + return shardingConfigReplAction(ctx, updates::URType::ResetOldShardingConfig, sourceId); } Error ReindexerImpl::resetShardingConfigCandidate(int64_t sourceId, const RdxContext& ctx) noexcept { - return shardingConfigReplAction(ctx, UpdateRecord::Type::ResetCandidateConfig, sourceId); + return shardingConfigReplAction(ctx, updates::URType::ResetCandidateConfig, sourceId); } Error ReindexerImpl::rollbackShardingConfigCandidate(int64_t sourceId, const RdxContext& ctx) noexcept { - return shardingConfigReplAction(ctx, UpdateRecord::Type::RollbackCandidateConfig, sourceId); + return shardingConfigReplAction(ctx, updates::URType::RollbackCandidateConfig, sourceId); } } // namespace reindexer diff --git a/cpp_src/core/reindexer_impl/reindexerimpl.h b/cpp_src/core/reindexer_impl/reindexerimpl.h index 0fe5d33b0..68d4b987f 100644 --- a/cpp_src/core/reindexer_impl/reindexerimpl.h +++ b/cpp_src/core/reindexer_impl/reindexerimpl.h @@ -14,23 +14,19 @@ #include "estl/atomic_unique_ptr.h" #include "estl/fast_hash_map.h" #include "estl/h_vector.h" +#include "events/observer.h" #include "net/ev/ev.h" #include "tools/errors.h" #include "tools/filecontentwatcher.h" #include "tools/nsversioncounter.h" #include "tools/tcmallocheapwathcher.h" -#include "replv3/updatesobserver.h" - namespace reindexer { class IClientsStats; struct ClusterControlRequestData; - -// REINDEX_WITH_V3_FOLLOWERS -class IUpdatesObserver; +class IUpdatesObserverV3; class UpdatesFilters; -// REINDEX_WITH_V3_FOLLOWERS namespace cluster { struct NodeData; @@ -109,7 +105,6 @@ class ReindexerImpl { Error Delete(std::string_view nsName, Item &item, LocalQueryResults &, const RdxContext &ctx); Error Delete(const Query &query, LocalQueryResults &result, const RdxContext &ctx); Error Select(const Query &query, LocalQueryResults &result, const RdxContext &ctx); - Error Commit(std::string_view nsName); Item NewItem(std::string_view nsName, const RdxContext &ctx); LocalTransaction NewTransaction(std::string_view nsName, const RdxContext &ctx); @@ -122,10 +117,10 @@ class ReindexerImpl { Error InitSystemNamespaces(); Error GetSqlSuggestions(std::string_view sqlQuery, int pos, std::vector &suggestions, const RdxContext &ctx); Error GetProtobufSchema(WrSerializer &ser, std::vector &namespaces); - Error GetReplState(std::string_view nsName, ReplicationStateV2 &state, const RdxContext &ctx); - Error SetClusterizationStatus(std::string_view nsName, const ClusterizationStatus &status, const RdxContext &ctx); - Error GetSnapshot(std::string_view nsName, const SnapshotOpts &opts, Snapshot &snapshot, const RdxContext &ctx); - Error ApplySnapshotChunk(std::string_view nsName, const SnapshotChunk &ch, const RdxContext &ctx); + Error GetReplState(std::string_view nsName, ReplicationStateV2 &state, const RdxContext &ctx) noexcept; + Error SetClusterizationStatus(std::string_view nsName, const ClusterizationStatus &status, const RdxContext &ctx) noexcept; + Error GetSnapshot(std::string_view nsName, const SnapshotOpts &opts, Snapshot &snapshot, const RdxContext &ctx) noexcept; + Error ApplySnapshotChunk(std::string_view nsName, const SnapshotChunk &ch, const RdxContext &ctx) noexcept; Error Status() noexcept { if rx_likely (connected_.load(std::memory_order_acquire)) { return {}; @@ -145,13 +140,17 @@ class ReindexerImpl { Error DumpIndex(std::ostream &os, std::string_view nsName, std::string_view index, const RdxContext &ctx); bool NamespaceIsInClusterConfig(std::string_view nsName); + Error SubscribeUpdates(IEventsObserver &observer, EventSubscriberConfig &&cfg); + Error UnsubscribeUpdates(IEventsObserver &observer); + // REINDEX_WITH_V3_FOLLOWERS - Error SubscribeUpdates(IUpdatesObserver *observer, const UpdatesFilters &filters, SubscriptionOpts opts); - Error UnsubscribeUpdates(IUpdatesObserver *observer); + Error SubscribeUpdates(IUpdatesObserverV3 *observer, const UpdatesFilters &filters, SubscriptionOpts opts); + Error UnsubscribeUpdates(IUpdatesObserverV3 *observer); // REINDEX_WITH_V3_FOLLOWERS private: using FilterNsNamesT = std::optional>; + using ShardinConfigPtr = intrusive_ptr>; class BackgroundThread { public: @@ -207,7 +206,7 @@ class ReindexerImpl { typedef contexted_shared_lock RLockT; typedef NsWLock WLockT; - Locker(cluster::INsDataReplicator &clusterizator, ReindexerImpl &owner) noexcept : clusterizator_(clusterizator), owner_(owner) {} + Locker(const cluster::IDataSyncer &clusterizator, ReindexerImpl &owner) noexcept : syncer_(clusterizator), owner_(owner) {} RLockT RLock(const RdxContext &ctx) const { return RLockT(mtx_, ctx); } WLockT DataWLock(const RdxContext &ctx) const { @@ -216,12 +215,12 @@ class ReindexerImpl { auto clusterStatus = owner_.clusterStatus_; const bool isFollowerDB = clusterStatus.role == ClusterizationStatus::Role::SimpleReplica || clusterStatus.role == ClusterizationStatus::Role::ClusterReplica; - bool synchronized = isFollowerDB || !requireSync || clusterizator_.IsInitialSyncDone(); + bool synchronized = isFollowerDB || !requireSync || syncer_.IsInitialSyncDone(); while (!synchronized) { lck.unlock(); - clusterizator_.AwaitInitialSync(ctx); + syncer_.AwaitInitialSync(ctx); lck.lock(); - synchronized = clusterizator_.IsInitialSyncDone(); + synchronized = syncer_.IsInitialSyncDone(); } return lck; } @@ -229,7 +228,7 @@ class ReindexerImpl { private: mutable Mutex mtx_; - cluster::INsDataReplicator &clusterizator_; + const cluster::IDataSyncer &syncer_; ReindexerImpl &owner_; }; @@ -286,7 +285,7 @@ class ReindexerImpl { [[nodiscard]] Error shardingConfigReplAction(const RdxContext &ctx, PreReplFunc func, Args &&...args) noexcept; template - [[nodiscard]] Error shardingConfigReplAction(const RdxContext &ctx, cluster::UpdateRecord::Type type, Args &&...args) noexcept; + [[nodiscard]] Error shardingConfigReplAction(const RdxContext &ctx, updates::URType type, Args &&...args) noexcept; fast_hash_map namespaces_; @@ -305,23 +304,32 @@ class ReindexerImpl { atomic_unique_ptr clusterConfig_; struct { auto Get() const noexcept { - std::lock_guard lk(m); - return config; + std::lock_guard lk(m_); + return config_; } void Set(std::optional &&other) noexcept { - std::lock_guard lk(m); - config.reset(other ? new intrusive_atomic_rc_wrapper(std::move(*other)) : nullptr); + std::lock_guard lk(m_); + config_.reset(other ? new intrusive_atomic_rc_wrapper(std::move(*other)) : nullptr); + if (handler_) { + handler_(config_); + } } operator bool() const noexcept { - std::lock_guard lk(m); - return config; + std::lock_guard lk(m_); + return config_; + } + void setHandled(std::function &&handler) { + std::lock_guard lk(m_); + assertrx_dbg(!handler_); + handler_ = std::move(handler); } private: - mutable spinlock m; - intrusive_ptr> config = nullptr; + mutable spinlock m_; + ShardinConfigPtr config_ = nullptr; + std::function handler_; } shardingConfig_; std::deque configWatchers_; @@ -344,12 +352,9 @@ class ReindexerImpl { NsVersionCounter nsVersion_; const CallbackMap proxyCallbacks_; - -#ifdef REINDEX_WITH_V3_FOLLOWERS UpdatesObservers observers_; -#endif // REINDEX_WITH_V3_FOLLOWERS + std::optional replCfgHandlerID_; - friend class Replicator; friend class cluster::DataReplicator; friend class cluster::ReplThread; friend class ClusterProxy; diff --git a/cpp_src/core/reindexer_impl/rx_selector.cc b/cpp_src/core/reindexer_impl/rx_selector.cc index 13ac8da4b..9aaf3e0c7 100644 --- a/cpp_src/core/reindexer_impl/rx_selector.cc +++ b/cpp_src/core/reindexer_impl/rx_selector.cc @@ -2,6 +2,7 @@ #include "core/nsselecter/nsselecter.h" #include "core/nsselecter/querypreprocessor.h" #include "core/queryresults/joinresults.h" +#include "estl/charset.h" #include "estl/restricted.h" #include "tools/logger.h" @@ -199,10 +200,10 @@ void RxSelector::DoSelect(const Query& q, LocalQueryResults& result, NsLocker } [[nodiscard]] static bool byJoinedField(std::string_view sortExpr, std::string_view joinedNs) { - static const fast_hash_set allowedSymbolsInIndexName{ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', - 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', '.', '+'}; + constexpr static estl::Charset kJoinedIndexNameSyms{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', + 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', + 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', '.', '+'}; std::string_view::size_type i = 0; const auto s = sortExpr.size(); while (i < s && isspace(sortExpr[i])) ++i; @@ -218,7 +219,7 @@ void RxSelector::DoSelect(const Query& q, LocalQueryResults& result, NsLocker } if (i >= s || sortExpr[i] != '.') return false; for (++i; i < s; ++i) { - if (allowedSymbolsInIndexName.find(sortExpr[i]) == allowedSymbolsInIndexName.end()) { + if (!kJoinedIndexNameSyms.test(sortExpr[i])) { if (isspace(sortExpr[i])) break; if (inQuotes && sortExpr[i] == '"') { inQuotes = false; @@ -234,33 +235,47 @@ void RxSelector::DoSelect(const Query& q, LocalQueryResults& result, NsLocker return i == s; } -bool RxSelector::isPreResultValuesModeOptimizationAvailable(const Query& jItemQ, const NamespaceImpl::Ptr& jns, const Query& mainQ) { - bool result = true; +StoredValuesOptimizationStatus RxSelector::isPreResultValuesModeOptimizationAvailable(const Query& jItemQ, const NamespaceImpl::Ptr& jns, + const Query& mainQ) { + auto status = StoredValuesOptimizationStatus::Enabled; jItemQ.Entries().VisitForEach([](const SubQueryEntry&) { assertrx_throw(0); }, [](const SubQueryFieldEntry&) { assertrx_throw(0); }, Skip{}, - [&jns, &result](const QueryEntry& qe) { + [&jns, &status](const QueryEntry& qe) { if (qe.IsFieldIndexed()) { assertrx_throw(jns->indexes_.size() > static_cast(qe.IndexNo())); const IndexType indexType = jns->indexes_[qe.IndexNo()]->Type(); - if (IsComposite(indexType) || IsFullText(indexType)) result = false; + if (IsComposite(indexType)) { + status = StoredValuesOptimizationStatus::DisabledByCompositeIndex; + } else if (IsFullText(indexType)) { + status = StoredValuesOptimizationStatus::DisabledByFullTextIndex; + } } }, - [&jns, &result](const BetweenFieldsQueryEntry& qe) { + [&jns, &status](const BetweenFieldsQueryEntry& qe) { if (qe.IsLeftFieldIndexed()) { assertrx_throw(jns->indexes_.size() > static_cast(qe.LeftIdxNo())); const IndexType indexType = jns->indexes_[qe.LeftIdxNo()]->Type(); - if (IsComposite(indexType) || IsFullText(indexType)) result = false; + if (IsComposite(indexType)) { + status = StoredValuesOptimizationStatus::DisabledByCompositeIndex; + } else if (IsFullText(indexType)) { + status = StoredValuesOptimizationStatus::DisabledByFullTextIndex; + } } if (qe.IsRightFieldIndexed()) { assertrx_throw(jns->indexes_.size() > static_cast(qe.RightIdxNo())); - if (IsComposite(jns->indexes_[qe.RightIdxNo()]->Type())) result = false; + if (IsComposite(jns->indexes_[qe.RightIdxNo()]->Type())) { + status = StoredValuesOptimizationStatus::DisabledByCompositeIndex; + } } }); - if (!result) return false; - for (const auto& se : mainQ.sortingEntries_) { - if (byJoinedField(se.expression, jItemQ.NsName())) return false; // TODO maybe allow #1410 + if (status == StoredValuesOptimizationStatus::Enabled) { + for (const auto& se : mainQ.sortingEntries_) { + if (byJoinedField(se.expression, jItemQ.NsName())) { + return StoredValuesOptimizationStatus::DisabledByJoinedFieldSort; // TODO maybe allow #1410 + } + } } - return true; + return status; } template @@ -317,7 +332,7 @@ VariantArray RxSelector::selectSubQuery(const Query& subQuery, const Query& main } } else { const auto fields = ns->indexes_[idxNo]->Fields(); - std::vector fieldsTypes; + QueryField::CompositeTypesVecT fieldsTypes; #ifndef NDEBUG const bool ftIdx = IsFullText(ns->indexes_[idxNo]->Type()); #endif @@ -325,10 +340,10 @@ VariantArray RxSelector::selectSubQuery(const Query& subQuery, const Query& main if (f == IndexValueType::SetByJsonPath) { // not indexed fields allowed only in ft composite indexes assertrx_throw(ftIdx); - fieldsTypes.push_back(KeyValueType::String{}); + fieldsTypes.emplace_back(KeyValueType::String{}); } else { assertrx_throw(f <= ns->indexes_.firstCompositePos()); - fieldsTypes.push_back(ns->indexes_[f]->SelectKeyType()); + fieldsTypes.emplace_back(ns->indexes_[f]->SelectKeyType()); } } for (const auto& it : qr) { @@ -394,8 +409,7 @@ JoinedSelectors RxSelector::prepareJoinedSelectors(const Query& q, LocalQueryRes assertrx_throw(ns); // For each joined queries - uint32_t joinedSelectorsCount = uint32_t(q.GetJoinQueries().size()); - for (size_t i = 0; i < q.GetJoinQueries().size(); ++i) { + for (size_t i = 0, jqCount = q.GetJoinQueries().size(); i < jqCount; ++i) { const auto& jq = q.GetJoinQueries()[i]; if rx_unlikely (isSystemNamespaceNameFast(jq.NsName())) { throw Error(errParams, "Queries to system namespaces ('%s') are not supported inside JOIN statement", jq.NsName()); @@ -425,8 +439,8 @@ JoinedSelectors RxSelector::prepareJoinedSelectors(const Query& q, LocalQueryRes jItemQ.Explain(q.NeedExplain()); jItemQ.Debug(jq.GetDebugLevel()).Limit(jq.Limit()); jItemQ.Strict(q.GetStrictMode()); - for (size_t i = 0; i < jq.sortingEntries_.size(); ++i) { - jItemQ.Sort(jq.sortingEntries_[i].expression, jq.sortingEntries_[i].desc); + for (const auto& jse : jq.sortingEntries_) { + jItemQ.Sort(jse.expression, jse.desc); } jItemQ.ReserveQueryEntries(jq.joinEntries_.size()); @@ -453,20 +467,20 @@ JoinedSelectors RxSelector::prepareJoinedSelectors(const Query& q, LocalQueryRes } JoinPreResult::CPtr preResult; if (joinRes.haveData) { - preResult = joinRes.it.val.preResult; + preResult = std::move(joinRes.it.val.preResult); } else { SelectCtxWithJoinPreSelect ctx(jjq, &q, JoinPreResultBuildCtx{std::make_shared()}); - ctx.preSelect.Result().enableStoredValues = isPreResultValuesModeOptimizationAvailable(jItemQ, jns, q); + ctx.preSelect.Result().storedValuesOptStatus = isPreResultValuesModeOptimizationAvailable(jItemQ, jns, q); ctx.functions = &func; ctx.requiresCrashTracking = true; LocalQueryResults jr; jns->Select(jr, ctx, rdxCtx); std::visit(overloaded{[&](JoinPreResult::Values& values) { - values.PreselectAllowed(static_cast(jns->Config().maxPreselectSize) >= values.size()); + values.PreselectAllowed(static_cast(jns->config().maxPreselectSize) >= values.size()); values.Lock(); }, Restricted{}([](const auto&) {})}, - ctx.preSelect.Result().preselectedPayload); + ctx.preSelect.Result().payload); preResult = ctx.preSelect.ResultPtr(); if (joinRes.needPut) { jns->putToJoinCache(joinRes, preResult); @@ -483,10 +497,9 @@ JoinedSelectors RxSelector::prepareJoinedSelectors(const Query& q, LocalQueryRes jns.reset(); }, Restricted{}([](const auto&) {})}, - preResult->preselectedPayload); + preResult->payload); joinedSelectors.emplace_back(jq.joinType, ns, std::move(jns), std::move(joinRes), std::move(jItemQ), result, jq, - JoinPreResultExecuteCtx{preResult}, joinedFieldIdx, func, joinedSelectorsCount, false, nsUpdateTime, - rdxCtx); + JoinPreResultExecuteCtx{preResult}, joinedFieldIdx, func, false, nsUpdateTime, rdxCtx); ThrowOnCancel(rdxCtx); } return joinedSelectors; diff --git a/cpp_src/core/reindexer_impl/rx_selector.h b/cpp_src/core/reindexer_impl/rx_selector.h index 10f850ae0..7be2b47a6 100644 --- a/cpp_src/core/reindexer_impl/rx_selector.h +++ b/cpp_src/core/reindexer_impl/rx_selector.h @@ -9,7 +9,7 @@ class SelectFunctionsHolder; class RxSelector { struct NsLockerItem { - NsLockerItem(NamespaceImpl::Ptr ins = {}) noexcept : ns(std::move(ins)), count(1) {} + explicit NsLockerItem(NamespaceImpl::Ptr ins = {}) noexcept : ns(std::move(ins)), count(1) {} NamespaceImpl::Ptr ns; NamespaceImpl::Locker::RLockT nsLck; unsigned count = 1; @@ -19,23 +19,23 @@ class RxSelector { template class NsLocker : private h_vector { public: - NsLocker(const Context &context) : context_(context) {} + explicit NsLocker(const Context &context) noexcept : context_(context) {} ~NsLocker() { // Unlock first - for (auto it = rbegin(); it != rend(); ++it) { - // Some of the namespaces may be in unlocked statet in case of the exception during Lock() call + for (auto it = rbegin(), re = rend(); it != re; ++it) { + // Some namespaces may be in unlocked state in case of the exception during Lock() call if (it->nsLck.owns_lock()) { it->nsLck.unlock(); } else { assertrx(!locked_); } } - // Clean (ns may releases, if locker holds last ref) + // Clean (ns may will release, if locker holds last ref) } - void Add(NamespaceImpl::Ptr ns) { + void Add(NamespaceImpl::Ptr &&ns) { assertrx(!locked_); - for (auto it = begin(); it != end(); ++it) { + for (auto it = begin(), e = end(); it != e; ++it) { if (it->ns.get() == ns.get()) { ++(it->count); return; @@ -43,10 +43,9 @@ class RxSelector { } emplace_back(std::move(ns)); - return; } - void Delete(const NamespaceImpl::Ptr &ns) { - for (auto it = begin(); it != end(); ++it) { + void Delete(const NamespaceImpl::Ptr &ns) noexcept { + for (auto it = begin(), e = end(); it != e; ++it) { if (it->ns.get() == ns.get()) { if (!--(it->count)) erase(it); return; @@ -63,11 +62,11 @@ class RxSelector { locked_ = true; } - NamespaceImpl::Ptr Get(const std::string &name) { - for (auto it = begin(); it != end(); it++) { + NamespaceImpl::Ptr Get(const std::string &name) noexcept { + for (auto it = begin(), e = end(); it != e; ++it) { if (iequals(it->ns->name_, name)) return it->ns; } - return nullptr; + return NamespaceImpl::Ptr(); } protected: @@ -94,7 +93,8 @@ class RxSelector { [[nodiscard]] static VariantArray selectSubQuery(const Query &subQuery, const Query &mainQuery, NsLocker &, LocalQueryResults &, SelectFunctionsHolder &, std::variant fieldOrKeys, std::vector &, const RdxContext &); - static bool isPreResultValuesModeOptimizationAvailable(const Query &jItemQ, const NamespaceImpl::Ptr &jns, const Query &mainQ); + static StoredValuesOptimizationStatus isPreResultValuesModeOptimizationAvailable(const Query &jItemQ, const NamespaceImpl::Ptr &jns, + const Query &mainQ); }; } // namespace reindexer diff --git a/cpp_src/core/reindexerconfig.h b/cpp_src/core/reindexerconfig.h index 6524b25ec..2da127009 100644 --- a/cpp_src/core/reindexerconfig.h +++ b/cpp_src/core/reindexerconfig.h @@ -2,18 +2,26 @@ #include #include +#include namespace reindexer { class IClientsStats; +class IExternalEventsListener; struct ReindexerConfig { ReindexerConfig& WithClientStats(IClientsStats* cs) noexcept { clientsStats = cs; return *this; } + ReindexerConfig& WithDBName(std::string _dbName) noexcept { + dbName = std::move(_dbName); + return *this; + } ReindexerConfig& WithUpdatesSize(size_t val) noexcept { - maxReplUpdatesSize = val; + if (val) { + maxReplUpdatesSize = val; + } return *this; } ReindexerConfig& WithAllocatorCacheLimits(int64_t cacheLimit, float maxCachePart) noexcept { @@ -24,7 +32,9 @@ struct ReindexerConfig { /// Object for receiving clients statistics IClientsStats* clientsStats = nullptr; - /// Max pended replication updates size in bytes + /// DB name to register in the events manager + std::string dbName; + /// Max pending replication updates size in bytes size_t maxReplUpdatesSize = 1024 * 1024 * 1024; /// Recommended maximum free cache size of tcmalloc memory allocator in bytes int64_t allocatorCacheLimit = -1; diff --git a/cpp_src/core/rollback.h b/cpp_src/core/rollback.h index a2808e8a4..aa6d867df 100644 --- a/cpp_src/core/rollback.h +++ b/cpp_src/core/rollback.h @@ -7,13 +7,13 @@ enum class NeedRollBack : bool { No = false, Yes = true }; class RollBackBase { protected: RollBackBase() noexcept = default; - ~RollBackBase() = default; + virtual ~RollBackBase() = default; RollBackBase(RollBackBase &&other) noexcept : disabled_{other.disabled_} { other.Disable(); } RollBackBase(const RollBackBase &) = delete; RollBackBase &operator=(const RollBackBase &) = delete; RollBackBase &operator=(RollBackBase &&) = delete; - void Disable() noexcept { disabled_ = true; } - bool IsDisabled() const noexcept { return disabled_; } + virtual void Disable() noexcept { disabled_ = true; } + [[nodiscard]] bool IsDisabled() const noexcept { return disabled_; } private: bool disabled_{false}; diff --git a/cpp_src/core/schema.cc b/cpp_src/core/schema.cc index b47f6c171..c1fdcbe9e 100644 --- a/cpp_src/core/schema.cc +++ b/cpp_src/core/schema.cc @@ -235,7 +235,9 @@ Error PrefixTree::buildProtobufSchema(ProtobufSchemaBuilder& builder, const Pref } bool buildTypesOnly = fieldsTypes_.NeedToEmbedType(node->props.xGoType); ProtobufSchemaBuilder object = builder.Object(fieldNumber, node->props.xGoType, buildTypesOnly); - buildProtobufSchema(object, *node, path, tm); + if (auto err = buildProtobufSchema(object, *node, path, tm); !err.ok()) { + return err; + } } builder.Field(name, fieldNumber, node->props); } @@ -322,7 +324,9 @@ void Schema::parseJsonNode(const gason::JsonNode& node, PrefixTree::PathT& split } field.isRequired = isRequired; if (!splittedPath.empty()) { - paths_.AddPath(std::move(field), splittedPath); + if (auto err = paths_.AddPath(std::move(field), splittedPath); !err.ok()) { + throw err; + } } else { paths_.root_.props = std::move(field); paths_.SetXGoType(node["x-go-type"].As()); diff --git a/cpp_src/core/selectfunc/ctx/basefunctionctx.h b/cpp_src/core/selectfunc/ctx/basefunctionctx.h index bb8f8e5f2..cebba4e26 100644 --- a/cpp_src/core/selectfunc/ctx/basefunctionctx.h +++ b/cpp_src/core/selectfunc/ctx/basefunctionctx.h @@ -1,39 +1,75 @@ #pragma once -#include -#include "core/selectfunc/selectfuncparser.h" -#include "estl/fast_hash_map.h" -#include "estl/fast_hash_set.h" +#include "core/selectfunc/functions/highlight.h" +#include "core/selectfunc/functions/snippet.h" namespace reindexer { template -std::shared_ptr reinterpret_pointer_cast(const std::shared_ptr& r) noexcept { - auto p = reinterpret_cast::element_type*>(r.get()); - return std::shared_ptr(r, p); +intrusive_ptr static_ctx_pointer_cast(const intrusive_ptr& r) noexcept { + assertrx_dbg(dynamic_cast(r.get()) != nullptr); + return intrusive_ptr(static_cast(r.get())); } -class BaseFunctionCtx { +class FuncNone { public: - typedef std::shared_ptr Ptr; + bool Process(ItemRef&, PayloadType&, const SelectFuncStruct&, std::vector&) noexcept { return false; } +}; + +template +constexpr std::size_t variant_index() { + static_assert(std::variant_size_v > index, "Type not found in variant"); + if constexpr (std::is_same_v, T>) { + return index; + } else { + return variant_index(); + } +} + +using SelectFuncVariant = std::variant; +enum class SelectFuncType { + None = variant_index(), + Snippet = variant_index(), + Highlight = variant_index(), + SnippetN = variant_index(), + + Max // Max possible value +}; + +class BaseFunctionCtx : public intrusive_atomic_rc_base { +public: + typedef intrusive_ptr Ptr; enum CtxType { kFtCtx = 0 }; virtual ~BaseFunctionCtx() {} - void AddFunction(const std::string& name, SelectFuncStruct::SelectFuncType functionIndx) { functions_[name].insert(functionIndx); } - bool CheckFunction(const std::string& name, std::initializer_list types) { - auto it = functions_.find(name); - - if (it == functions_.end()) return false; - for (auto t : types) { - auto fit = it->second.find(t); - if (fit != it->second.end()) return true; + void AddFunction(const std::string& name, SelectFuncType functionIndx) { + auto it = std::find_if(functions_.begin(), functions_.end(), [&name](const FuncData& data) { return data.name == name; }); + auto& ref = (it == functions_.end()) ? functions_.emplace_back(std::string(name)) : *it; + ref.types[static_cast(functionIndx)] = true; + } + bool CheckFunction(const std::string& name, std::initializer_list types) { + auto it = std::find_if(functions_.begin(), functions_.end(), [&name](const FuncData& data) { return data.name == name; }); + if (it != functions_.end()) { + for (auto t : types) { + if (it->types[static_cast(t)]) { + return true; + } + } } return false; } CtxType type; -protected: - fast_hash_map>> functions_; +private: + struct FuncData { + using TypesArrayT = std::array(SelectFuncType::Max)>; + + FuncData(std::string&& _name) noexcept : name(std::move(_name)) {} + + std::string name; + TypesArrayT types{}; + }; + h_vector functions_; }; } // namespace reindexer diff --git a/cpp_src/core/selectfunc/ctx/ftctx.cc b/cpp_src/core/selectfunc/ctx/ftctx.cc index 304acaabb..c185dc124 100644 --- a/cpp_src/core/selectfunc/ctx/ftctx.cc +++ b/cpp_src/core/selectfunc/ctx/ftctx.cc @@ -2,83 +2,79 @@ namespace reindexer { -FtCtx::FtCtx() { - data_ = std::make_shared(); - this->type = BaseFunctionCtx::kFtCtx; -} - -int16_t FtCtx::Proc(size_t pos) { - if (pos >= data_->proc_.size()) return 0; - return data_->proc_[pos]; -} -void FtCtx::Reserve(size_t size) { data_->proc_.reserve(size); } - -size_t FtCtx::Size() const noexcept { return data_->proc_.size(); } - -bool FtCtx::NeedArea() const noexcept { return data_->need_area_; } - bool FtCtx::PrepareAreas(const RHashMap &fields, const std::string &name) { - if (!fields.empty()) data_->is_composite_ = true; + assertrx_dbg(!NeedArea()); + auto &data = *data_; + if (!fields.empty()) { + data.isComposite_ = true; + } - if (data_->is_composite_) { + bool needArea = false; + if (data.isComposite_) { for (auto &field : fields) { - data_->need_area_ = - CheckFunction(field.first, {SelectFuncStruct::SelectFuncType::Snippet, SelectFuncStruct::SelectFuncType::SnippetN, - SelectFuncStruct::SelectFuncType::Highlight}); - if (data_->need_area_) return true; + needArea = CheckFunction(field.first, {SelectFuncType::Snippet, SelectFuncType::SnippetN, SelectFuncType::Highlight}); + if (needArea) { + break; + } } } - data_->need_area_ = CheckFunction(name, {SelectFuncStruct::SelectFuncType::Snippet, SelectFuncStruct::SelectFuncType::SnippetN, - SelectFuncStruct::SelectFuncType::Highlight}); - return data_->need_area_; + needArea = needArea || CheckFunction(name, {SelectFuncType::Snippet, SelectFuncType::SnippetN, SelectFuncType::Highlight}); + if (needArea) { + data.InitHolders(); + } + return needArea; } template void FtCtx::Add(InputIterator begin, InputIterator end, int16_t proc, AreaHolder &&holder) { - data_->area_.emplace_back(std::move(holder)); + auto &data = *data_; + data.area_.emplace_back(std::move(holder)); for (; begin != end; ++begin) { - data_->proc_.push_back(proc); - if (data_->need_area_) { - data_->holders_.emplace(*begin, data_->area_.size() - 1); + data.proc_.emplace_back(proc); + if (data.holders_.has_value()) { + data.holders_->emplace(*begin, data_->area_.size() - 1); } } } template void FtCtx::Add(InputIterator begin, InputIterator end, int16_t proc) { + auto &data = *data_; for (; begin != end; ++begin) { - data_->proc_.push_back(proc); + data.proc_.emplace_back(proc); } } template void FtCtx::Add(InputIterator begin, InputIterator end, int16_t proc, const std::vector &mask, AreaHolder &&holder) { - data_->area_.emplace_back(std::move(holder)); + auto &data = *data_; + data.area_.emplace_back(std::move(holder)); for (; begin != end; ++begin) { assertrx(static_cast(*begin) < mask.size()); if (!mask[*begin]) continue; - data_->proc_.push_back(proc); - if (data_->need_area_) { - data_->holders_.emplace(*begin, data_->area_.size() - 1); + data.proc_.emplace_back(proc); + if (data.holders_.has_value()) { + data.holders_->emplace(*begin, data.area_.size() - 1); } } } template void FtCtx::Add(InputIterator begin, InputIterator end, int16_t proc, const std::vector &mask) { + auto &data = *data_; for (; begin != end; ++begin) { assertrx(static_cast(*begin) < mask.size()); if (!mask[*begin]) continue; - data_->proc_.push_back(proc); + data.proc_.emplace_back(proc); } } -template void FtCtx::Add::iterator>(span::iterator begin, span::iterator end, int16_t proc, - AreaHolder &&holder); -template void FtCtx::Add::iterator>(span::iterator begin, span::iterator end, int16_t proc, - const std::vector &, AreaHolder &&holder); -template void FtCtx::Add::iterator>(span::iterator begin, span::iterator end, int16_t proc); -template void FtCtx::Add::iterator>(span::iterator begin, span::iterator end, int16_t proc, - const std::vector &); +template void FtCtx::Add::iterator>(span::iterator begin, span::iterator end, int16_t proc, + AreaHolder &&holder); +template void FtCtx::Add::iterator>(span::iterator begin, span::iterator end, int16_t proc, + const std::vector &, AreaHolder &&holder); +template void FtCtx::Add::iterator>(span::iterator begin, span::iterator end, int16_t proc); +template void FtCtx::Add::iterator>(span::iterator begin, span::iterator end, int16_t proc, + const std::vector &); } // namespace reindexer diff --git a/cpp_src/core/selectfunc/ctx/ftctx.h b/cpp_src/core/selectfunc/ctx/ftctx.h index 225b21b3d..bc85c3218 100644 --- a/cpp_src/core/selectfunc/ctx/ftctx.h +++ b/cpp_src/core/selectfunc/ctx/ftctx.h @@ -10,22 +10,25 @@ namespace reindexer { class FtCtx : public BaseFunctionCtx { public: - typedef std::shared_ptr Ptr; - struct Data { - typedef std::shared_ptr Ptr; + typedef intrusive_ptr Ptr; + struct Data : public BaseFunctionCtx { + bool NeedArea() const noexcept { return holders_.has_value(); } + void InitHolders() { + assertrx_dbg(!holders_.has_value()); + holders_.emplace(); + } + + typedef intrusive_ptr Ptr; std::vector proc_; - fast_hash_map holders_; + std::optional> holders_; std::vector area_; - bool need_area_ = false; - bool is_composite_ = false; + bool isComposite_ = false; bool isWordPositions_ = false; std::string extraWordSymbols_; }; - FtCtx(); - int16_t Proc(size_t pos); - bool isComposite() const noexcept { return data_->is_composite_; } - size_t GetSize() const noexcept { return data_->proc_.size(); } + FtCtx() : data_(make_intrusive()) { this->type = BaseFunctionCtx::kFtCtx; } + int16_t Proc(size_t pos) const noexcept { return (pos < data_->proc_.size()) ? data_->proc_[pos] : 0; } template void Add(InputIterator begin, InputIterator end, int16_t proc, AreaHolder &&holder); @@ -37,9 +40,9 @@ class FtCtx : public BaseFunctionCtx { template void Add(InputIterator begin, InputIterator end, int16_t proc, const std::vector &mask); - void Reserve(size_t size); - size_t Size() const noexcept; - bool NeedArea() const noexcept; + void Reserve(size_t size) { data_->proc_.reserve(size); } + size_t Size() const noexcept { return data_->proc_.size(); } + bool NeedArea() const noexcept { return data_->NeedArea(); } bool PrepareAreas(const RHashMap &fields, const std::string &name); void SetData(Data::Ptr data) noexcept { data_ = std::move(data); } diff --git a/cpp_src/core/selectfunc/functionexecutor.h b/cpp_src/core/selectfunc/functionexecutor.h index e902a4b3a..9fab27bcc 100644 --- a/cpp_src/core/selectfunc/functionexecutor.h +++ b/cpp_src/core/selectfunc/functionexecutor.h @@ -1,6 +1,6 @@ #pragma once -#include "cluster/updaterecord.h" +#include "updates/updaterecord.h" #include "core/keyvalue/variant.h" namespace reindexer { @@ -11,12 +11,12 @@ class NsContext; class FunctionExecutor { public: - explicit FunctionExecutor(NamespaceImpl& ns, h_vector& replUpdates) noexcept : ns_(ns), replUpdates_(replUpdates) {} + explicit FunctionExecutor(NamespaceImpl& ns, h_vector& replUpdates) noexcept : ns_(ns), replUpdates_(replUpdates) {} Variant Execute(SelectFuncStruct& funcData, const NsContext& ctx); private: NamespaceImpl& ns_; - h_vector& replUpdates_; + h_vector& replUpdates_; }; } // namespace reindexer diff --git a/cpp_src/core/selectfunc/functions/highlight.cc b/cpp_src/core/selectfunc/functions/highlight.cc index 4e4177843..508b51532 100644 --- a/cpp_src/core/selectfunc/functions/highlight.cc +++ b/cpp_src/core/selectfunc/functions/highlight.cc @@ -3,6 +3,8 @@ #include "core/keyvalue/p_string.h" #include "core/payload/payloadiface.h" #include "core/selectfunc/ctx/ftctx.h" +#include "core/selectfunc/selectfuncparser.h" + namespace reindexer { bool Highlight::Process(ItemRef &res, PayloadType &pl_type, const SelectFuncStruct &func, std::vector &stringsHolder) { @@ -10,10 +12,13 @@ bool Highlight::Process(ItemRef &res, PayloadType &pl_type, const SelectFuncStru if (!func.ctx || func.ctx->type != BaseFunctionCtx::kFtCtx) return false; - FtCtx::Ptr ftctx = reindexer::reinterpret_pointer_cast(func.ctx); - auto dataFtCtx = ftctx->GetData(); - auto it = dataFtCtx->holders_.find(res.Id()); - if (it == dataFtCtx->holders_.end()) { + FtCtx::Ptr ftctx = reindexer::static_ctx_pointer_cast(func.ctx); + auto &dataFtCtx = *ftctx->GetData(); + if (!dataFtCtx.holders_.has_value()) { + return false; + } + auto it = dataFtCtx.holders_->find(res.Id()); + if (it == dataFtCtx.holders_->end()) { return false; } @@ -31,7 +36,7 @@ bool Highlight::Process(ItemRef &res, PayloadType &pl_type, const SelectFuncStru } const std::string *data = p_string(kr[0]).getCxxstr(); - auto pva = dataFtCtx->area_[it->second].GetAreas(func.fieldNo); + auto pva = dataFtCtx.area_[it->second].GetAreas(func.fieldNo); if (!pva || pva->Empty()) return false; auto &va = *pva; diff --git a/cpp_src/core/selectfunc/functions/snippet.cc b/cpp_src/core/selectfunc/functions/snippet.cc index c1c2de718..0f3bc28db 100644 --- a/cpp_src/core/selectfunc/functions/snippet.cc +++ b/cpp_src/core/selectfunc/functions/snippet.cc @@ -3,6 +3,7 @@ #include "core/keyvalue/p_string.h" #include "core/payload/payloadiface.h" #include "core/selectfunc/ctx/ftctx.h" +#include "core/selectfunc/selectfuncparser.h" #include "highlight.h" #include "tools/errors.h" #include "utf8cpp/utf8.h" @@ -256,16 +257,19 @@ bool Snippet::Process(ItemRef &res, PayloadType &pl_type, const SelectFuncStruct if (!func.ctx) return false; init(func); - FtCtx::Ptr ftctx = reindexer::reinterpret_pointer_cast(func.ctx); - auto dataFtCtx = ftctx->GetData(); - if (!dataFtCtx->isWordPositions_) { + FtCtx::Ptr ftctx = reindexer::static_ctx_pointer_cast(func.ctx); + auto &dataFtCtx = *ftctx->GetData(); + if (!dataFtCtx.isWordPositions_) { throw Error(errParams, "Snippet function does not work with ft_fuzzy index."); } if (!func.tagsPath.empty()) { throw Error(errConflict, "SetByJsonPath is not implemented yet!"); } - auto it = dataFtCtx->holders_.find(res.Id()); - if (it == dataFtCtx->holders_.end()) { + if (!dataFtCtx.holders_.has_value()) { + return false; + } + auto it = dataFtCtx.holders_->find(res.Id()); + if (it == dataFtCtx.holders_->end()) { return false; } Payload pl(pl_type, res.Value()); @@ -277,7 +281,7 @@ bool Snippet::Process(ItemRef &res, PayloadType &pl_type, const SelectFuncStruct } const std::string *data = p_string(kr[0]).getCxxstr(); - auto pva = dataFtCtx->area_[it->second].GetAreas(func.fieldNo); + auto pva = dataFtCtx.area_[it->second].GetAreas(func.fieldNo); if (!pva || pva->Empty()) return false; std::string resultString; diff --git a/cpp_src/core/selectfunc/nsselectfuncinterface.cc b/cpp_src/core/selectfunc/nsselectfuncinterface.cc index e5e470865..1f832cea7 100644 --- a/cpp_src/core/selectfunc/nsselectfuncinterface.cc +++ b/cpp_src/core/selectfunc/nsselectfuncinterface.cc @@ -3,7 +3,6 @@ #include "core/namespace/namespaceimpl.h" namespace reindexer { -const std::string& NsSelectFuncInterface::GetName() const noexcept { return nm_.name_; }; int NsSelectFuncInterface::getIndexByName(std::string_view index) const noexcept { return nm_.getIndexByName(index); } bool NsSelectFuncInterface::getIndexByName(std::string_view name, int& index) const noexcept { return nm_.tryGetIndexByName(name, index); } int NsSelectFuncInterface::getIndexesCount() const noexcept { return nm_.indexes_.size(); } diff --git a/cpp_src/core/selectfunc/nsselectfuncinterface.h b/cpp_src/core/selectfunc/nsselectfuncinterface.h index 7bab47f88..c69c75324 100644 --- a/cpp_src/core/selectfunc/nsselectfuncinterface.h +++ b/cpp_src/core/selectfunc/nsselectfuncinterface.h @@ -10,7 +10,6 @@ class FieldsSet; class NsSelectFuncInterface { public: explicit NsSelectFuncInterface(const NamespaceImpl& nm) noexcept : nm_(nm) {} - const std::string& GetName() const noexcept; int getIndexByName(std::string_view index) const noexcept; bool getIndexByName(std::string_view name, int& index) const noexcept; int getIndexesCount() const noexcept; diff --git a/cpp_src/core/selectfunc/selectfunc.cc b/cpp_src/core/selectfunc/selectfunc.cc index 730c3b974..c680edf0a 100644 --- a/cpp_src/core/selectfunc/selectfunc.cc +++ b/cpp_src/core/selectfunc/selectfunc.cc @@ -33,19 +33,18 @@ SelectFunction::Ptr SelectFunctionsHolder::AddNamespace(const Query &q, const Na if (queries_.size() <= nsid) { queries_.resize(nsid + 1); } - queries_[nsid] = std::make_shared(q, NsSelectFuncInterface(nm)); + queries_[nsid] = make_intrusive(q, NsSelectFuncInterface(nm)); return queries_[nsid]; } SelectFunction::SelectFunction(const Query &q, NsSelectFuncInterface &&nm) : nm_(std::move(nm)), currCjsonFieldIdx_(nm_.getIndexesCount()) { - functions_.reserve(q.selectFunctions_.size()); for (auto &func : q.selectFunctions_) { SelectFuncParser parser; SelectFuncStruct &result = parser.Parse(func); if (!result.isFunction) continue; createFunc(result); } -}; +} void SelectFunction::createFunc(SelectFuncStruct &data) { int indexNo = IndexValueType::NotSet; @@ -60,8 +59,6 @@ void SelectFunction::createFunc(SelectFuncStruct &data) { // if index is composite then create function for inner use only if (IsComposite(nm_.getIndexType(indexNo))) { - std::vector subIndexes; - int fieldNo = 0; const FieldsSet &fields = nm_.getIndexFields(indexNo); @@ -205,12 +202,12 @@ bool SelectFunction::ProcessItem(ItemRef &res, PayloadType &pl_type, std::vector BaseFunctionCtx::Ptr SelectFunction::createCtx(SelectFuncStruct &data, BaseFunctionCtx::Ptr ctx, IndexType index_type) { if (IsFullText(index_type)) { if (!ctx) { - data.ctx = std::make_shared(); + data.ctx = make_intrusive(); } else { data.ctx = std::move(ctx); } const std::string &indexName = (data.indexNo >= nm_.getIndexesCount()) ? data.field : nm_.getIndexName(data.indexNo); - data.ctx->AddFunction(indexName, SelectFuncStruct::SelectFuncType(data.func.index())); + data.ctx->AddFunction(indexName, SelectFuncType(data.func.index())); } return data.ctx; } diff --git a/cpp_src/core/selectfunc/selectfunc.h b/cpp_src/core/selectfunc/selectfunc.h index d5f09da96..169a54106 100644 --- a/cpp_src/core/selectfunc/selectfunc.h +++ b/cpp_src/core/selectfunc/selectfunc.h @@ -1,17 +1,17 @@ #pragma once #include "core/query/query.h" #include "core/queryresults/queryresults.h" -#include "ctx/basefunctionctx.h" #include "nsselectfuncinterface.h" +#include "selectfuncparser.h" namespace reindexer { class NamespaceImpl; /// Represents sql function in a query /// (like avg(x) or sum(x)). -class SelectFunction { +class SelectFunction : public intrusive_atomic_rc_base { public: - typedef std::shared_ptr Ptr; + typedef intrusive_ptr Ptr; SelectFunction(const Query& q, NsSelectFuncInterface&& nm); /// Processes selected item to apply sql function. diff --git a/cpp_src/core/selectfunc/selectfuncparser.h b/cpp_src/core/selectfunc/selectfuncparser.h index 91a4b045a..6e2427239 100644 --- a/cpp_src/core/selectfunc/selectfuncparser.h +++ b/cpp_src/core/selectfunc/selectfuncparser.h @@ -5,46 +5,22 @@ #include #include #include +#include "ctx/basefunctionctx.h" #include "estl/tokenizer.h" -#include "functions/highlight.h" -#include "functions/snippet.h" namespace reindexer { class BaseFunctionCtx; -class FuncNone { -public: - bool Process(ItemRef &, PayloadType &, const SelectFuncStruct &, std::vector &) noexcept { return false; } -}; - -template -constexpr std::size_t variant_index() { - static_assert(std::variant_size_v > index, "Type not found in variant"); - if constexpr (std::is_same_v, T>) { - return index; - } else { - return variant_index(); - } -} - struct SelectFuncStruct { - using FuncVariant = std::variant; - enum class SelectFuncType { - None = variant_index(), - Snippet = variant_index(), - Highlight = variant_index(), - SnippetN = variant_index() - }; - - FuncVariant func; + SelectFuncVariant func; bool isFunction = false; std::string field; std::string value; std::string funcName; std::vector funcArgs; std::unordered_map namedArgs; - std::shared_ptr ctx; + BaseFunctionCtx::Ptr ctx; TagsPath tagsPath; int indexNo = -1; int fieldNo = 0; diff --git a/cpp_src/core/selectkeyresult.h b/cpp_src/core/selectkeyresult.h index 03f0cf14f..665f1b8af 100644 --- a/cpp_src/core/selectkeyresult.h +++ b/cpp_src/core/selectkeyresult.h @@ -36,7 +36,7 @@ class SingleSelectKeyResult { } } explicit SingleSelectKeyResult(IdSet::Ptr &&ids) noexcept : tempIds_(std::move(ids)), ids_(*tempIds_) {} - explicit SingleSelectKeyResult(const IdSetRef &ids) noexcept : ids_(ids) {} + explicit SingleSelectKeyResult(IdSetCRef ids) noexcept : ids_(ids) {} explicit SingleSelectKeyResult(IdType rBegin, IdType rEnd) noexcept : rBegin_(rBegin), rEnd_(rEnd), isRange_(true) {} SingleSelectKeyResult(const SingleSelectKeyResult &other) noexcept : tempIds_(other.tempIds_), @@ -97,14 +97,14 @@ class SingleSelectKeyResult { } IdSet::Ptr tempIds_; - IdSetRef ids_; + IdSetCRef ids_; protected: const base_idsetset *set_ = nullptr; union { - IdSetRef::const_iterator begin_; - IdSetRef::const_reverse_iterator rbegin_; + IdSetCRef::const_iterator begin_; + IdSetCRef::const_reverse_iterator rbegin_; base_idsetset::const_iterator setbegin_; base_idsetset::const_reverse_iterator setrbegin_; int rBegin_ = 0; @@ -112,8 +112,8 @@ class SingleSelectKeyResult { }; union { - IdSetRef::const_iterator end_; - IdSetRef::const_reverse_iterator rend_; + IdSetCRef::const_iterator end_; + IdSetCRef::const_reverse_iterator rend_; base_idsetset::const_iterator setend_; base_idsetset::const_reverse_iterator setrend_; int rEnd_ = 0; @@ -121,8 +121,8 @@ class SingleSelectKeyResult { }; union { - IdSetRef::const_iterator it_; - IdSetRef::const_reverse_iterator rit_; + IdSetCRef::const_iterator it_; + IdSetCRef::const_reverse_iterator rit_; base_idsetset::const_iterator itset_; base_idsetset::const_reverse_iterator ritset_; int rIt_ = 0; diff --git a/cpp_src/core/shardingproxy.cc b/cpp_src/core/shardingproxy.cc index de9c4263f..824219a85 100644 --- a/cpp_src/core/shardingproxy.cc +++ b/cpp_src/core/shardingproxy.cc @@ -229,16 +229,16 @@ Error ShardingProxy::handleNewShardingConfig(const gason::JsonNode &configJSON, checkSyncCluster(config); - auto router = sharding::LocatorService(impl_, config); - err = router.Start(); + auto router = std::make_unique(impl_, config); + err = router->Start(); if (!err.ok()) return err; - auto connections = router.GetAllShardsConnections(err); + auto connections = router->GetAllShardsConnections(err); if (!err.ok()) return err; auto sourceId = generateSourceId(); - ParallelExecutor execNodes(router.ActualShardId()); + ParallelExecutor execNodes(router->ActualShardId()); std::unordered_map cfgsStorage; @@ -422,7 +422,7 @@ Error ShardingProxy::ShardingControlRequest(const sharding::ShardingControlReque assertrx(!ctx.GetOriginLSN().isEmpty()); const auto &data = std::get(request.data); return data.config.empty() ? handleNewShardingConfigLocally(gason::JsonNode::EmptyNode(), std::optional(), ctx) - : handleNewShardingConfigLocally<>(data.config, data.sourceId, ctx); + : handleNewShardingConfigLocally>(std::string(data.config), data.sourceId, ctx); } default: throw Error(errLogic, "Unsupported sharding request command: %d", int(request.type)); @@ -434,7 +434,7 @@ Error ShardingProxy::ShardingControlRequest(const sharding::ShardingControlReque void ShardingProxy::saveShardingCfgCandidate(const sharding::SaveConfigCommand &data, const RdxContext &ctx) { cluster::ShardingConfig config; - auto err = config.FromJSON(data.config); + auto err = config.FromJSON(std::string(data.config)); if (!err.ok()) throw err; saveShardingCfgCandidateImpl(std::move(config), data.sourceId, ctx); @@ -607,7 +607,7 @@ void ShardingProxy::checkSyncCluster(const cluster::ShardingConfig &shardingConf if (!err.ok()) throw err; cluster::ReplicationStats stats; - err = stats.FromJSON(wser.Slice()); + err = stats.FromJSON(giftStr(wser.Slice())); if (!err.ok()) throw err; if (!stats.nodeStats.empty() && stats.nodeStats.size() != hosts.size()) { @@ -652,12 +652,12 @@ void ShardingProxy::applyNewShardingConfig(const sharding::ApplyConfigCommand &d impl_.SaveNewShardingConfigFile(*config); - auto err = impl_.ResetShardingConfig(std::move(*config)); + auto err = impl_.ResetShardingConfig(std::move(config)); // TODO: after allowing actions to upsert #config namespace, make ApplyNewShardingConfig returned void, allow except here // if (!err.ok()) return err; - lockedShardingRouter = std::make_shared(impl_, *impl_.GetShardingConfig()); config = std::nullopt; + lockedShardingRouter = std::make_shared(impl_, *impl_.GetShardingConfig()); err = lockedShardingRouter->Start(); if (err.ok()) { @@ -1087,20 +1087,6 @@ Error ShardingProxy::Select(const Query &query, QueryResults &result, unsigned p } } -Error ShardingProxy::Commit(std::string_view nsName, const RdxContext &ctx) { - try { - auto localCommit = [this](std::string_view nsName) { return impl_.Commit(nsName); }; - if (auto lckRouterOpt = isWithSharding(nsName, ctx)) { - Error err = (*lckRouterOpt)->AwaitShards(ctx); - if (!err.ok()) return err; - return delegateToShardsByNs(*lckRouterOpt, ctx, &client::Reindexer::Commit, localCommit, nsName); - } - return localCommit(nsName); - } catch (Error &e) { - return e; - } -} - auto ShardingProxy::ShardingRouter::SharedPtr(const RdxContext &ctx) const { auto lk = SharedLock(ctx); return locatorService_; @@ -1190,9 +1176,7 @@ Error ShardingProxy::EnumMeta(std::string_view nsName, std::vector Error ShardingProxy::DeleteMeta(std::string_view nsName, const std::string &key, const RdxContext &ctx) { try { - auto localDeleteMeta = [this, &ctx](std::string_view nsName, const std::string &key) { - return impl_.DeleteMeta(nsName, key, ctx); - }; + auto localDeleteMeta = [this, &ctx](std::string_view nsName, const std::string &key) { return impl_.DeleteMeta(nsName, key, ctx); }; if (auto lckRouterOpt = isWithSharding(nsName, ctx)) { return delegateToShards(*lckRouterOpt, ctx, &client::Reindexer::DeleteMeta, localDeleteMeta, nsName, key); diff --git a/cpp_src/core/shardingproxy.h b/cpp_src/core/shardingproxy.h index bb7392072..37cbd7a56 100644 --- a/cpp_src/core/shardingproxy.h +++ b/cpp_src/core/shardingproxy.h @@ -45,7 +45,6 @@ class ShardingProxy { Error Delete(const Query &query, QueryResults &result, const RdxContext &ctx); Error Select(std::string_view sql, QueryResults &result, unsigned proxyFetchLimit, const RdxContext &ctx); Error Select(const Query &query, QueryResults &result, unsigned proxyFetchLimit, const RdxContext &ctx); - Error Commit(std::string_view nsName, const RdxContext &ctx); Item NewItem(std::string_view nsName, const RdxContext &ctx) { return impl_.NewItem(nsName, ctx); } Transaction NewTransaction(std::string_view nsName, const RdxContext &ctx); @@ -134,11 +133,16 @@ class ShardingProxy { [[nodiscard]] Error ShardingControlRequest(const sharding::ShardingControlRequestData &request, const RdxContext &ctx) noexcept; + Error SubscribeUpdates(IEventsObserver &observer, EventSubscriberConfig &&cfg) { + return impl_.SubscribeUpdates(observer, std::move(cfg)); + } + Error UnsubscribeUpdates(IEventsObserver &observer) { return impl_.UnsubscribeUpdates(observer); } + // REINDEX_WITH_V3_FOLLOWERS - Error SubscribeUpdates(IUpdatesObserver *observer, const UpdatesFilters &filters, SubscriptionOpts opts) { + Error SubscribeUpdates(IUpdatesObserverV3 *observer, const UpdatesFilters &filters, SubscriptionOpts opts) { return impl_.SubscribeUpdates(observer, filters, opts); } - Error UnsubscribeUpdates(IUpdatesObserver *observer) { return impl_.UnsubscribeUpdates(observer); } + Error UnsubscribeUpdates(IUpdatesObserverV3 *observer) { return impl_.UnsubscribeUpdates(observer); } // REINDEX_WITH_V3_FOLLOWERS private: @@ -183,12 +187,12 @@ class ShardingProxy { Error executeQueryOnClient(client::Reindexer &connection, const Query &q, client::QueryResults &qrClient, const CalucalteFT &limitOffsetCalc); - [[nodiscard]] Error handleNewShardingConfig(const gason::JsonNode &config, const RdxContext &ctx) noexcept; - [[nodiscard]] Error handleNewShardingConfigLocally(const gason::JsonNode &config, std::optional externalSourceId, - const RdxContext &ctx) noexcept; + Error handleNewShardingConfig(const gason::JsonNode &config, const RdxContext &ctx) noexcept; + Error handleNewShardingConfigLocally(const gason::JsonNode &config, std::optional externalSourceId, + const RdxContext &ctx) noexcept; template - [[nodiscard]] Error handleNewShardingConfigLocally(const ConfigType &rawConfig, std::optional externalSourceId, - const RdxContext &ctx) noexcept; + Error handleNewShardingConfigLocally(const ConfigType &rawConfig, std::optional externalSourceId, + const RdxContext &ctx) noexcept; void saveShardingCfgCandidate(const sharding::SaveConfigCommand &requestData, const RdxContext &ctx); void saveShardingCfgCandidateImpl(cluster::ShardingConfig config, int64_t sourceId, const RdxContext &ctx); @@ -206,7 +210,7 @@ class ShardingProxy { template void resetOrRollbackShardingConfig(const sharding::ResetConfigCommand &data, const RdxContext &ctx); template - [[nodiscard]] Error resetShardingConfigs(int64_t sourceId, const RdxContext &ctx) noexcept; + Error resetShardingConfigs(int64_t sourceId, const RdxContext &ctx) noexcept; void obtainConfigForResetRouting(std::optional &config, ConfigResetFlag resetFlag, const RdxContext &ctx) const; diff --git a/cpp_src/core/sorting/sortexpression.cc b/cpp_src/core/sorting/sortexpression.cc index 75d724926..fd4e57dd8 100644 --- a/cpp_src/core/sorting/sortexpression.cc +++ b/cpp_src/core/sorting/sortexpression.cc @@ -4,14 +4,14 @@ #include "core/nsselecter/joinedselector.h" #include "core/nsselecter/joinedselectormock.h" #include "core/queryresults/joinresults.h" -#include "estl/fast_hash_set.h" +#include "estl/charset.h" #include "estl/restricted.h" #include "tools/stringstools.h" #include "vendor/double-conversion/double-conversion.h" namespace { -static void throwParseError(std::string_view sortExpr, char const* const pos, std::string_view message) { +static RX_NO_INLINE void throwParseError(std::string_view sortExpr, char const* const pos, std::string_view message) { throw reindexer::Error(errParams, "'%s' is not valid sort expression. Parser failed at position %d.%s%s", sortExpr, pos - sortExpr.data(), message.empty() ? "" : " ", message); } @@ -55,7 +55,7 @@ using SortExprFuncs::DistanceBetweenJoinedIndexesSameNs; const PayloadValue& SortExpression::getJoinedValue(IdType rowId, const joins::NamespaceResults& joinResults, const std::vector& joinedSelectors, size_t nsIdx) { - assertrx(joinedSelectors.size() > nsIdx); + assertrx_throw(joinedSelectors.size() > nsIdx); const auto& js = joinedSelectors[nsIdx]; const joins::ItemIterator jIt{&joinResults, rowId}; const auto jfIt = jIt.at(nsIdx); @@ -72,14 +72,14 @@ VariantArray SortExpression::GetJoinedFieldValues(IdType rowId, const joins::Nam overloaded{ [](const JoinPreResult::Values& values) noexcept { return std::cref(values.payloadType); }, Restricted{}([&js](const auto&) noexcept { return std::cref(js.rightNs_->payloadType_); })}, - js.PreResult().preselectedPayload); + js.PreResult().payload); const ConstPayload pv{pt, getJoinedValue(rowId, joinResults, joinedSelectors, nsIdx)}; VariantArray values; if (index == IndexValueType::SetByJsonPath) { TagsMatcher tm = std::visit(overloaded{[](const JoinPreResult::Values& values) noexcept { return std::cref(values.tagsMatcher); }, Restricted{}( [&js](const auto&) noexcept { return std::cref(js.rightNs_->tagsMatcher_); })}, - js.PreResult().preselectedPayload); + js.PreResult().payload); pv.GetByJsonPath(column, tm, values, KeyValueType::Undefined{}); } else { pv.Get(index, values); @@ -98,7 +98,7 @@ bool SortExpression::ByJoinedField() const noexcept { } SortExprFuncs::JoinedIndex& SortExpression::GetJoinedIndex() noexcept { - assertrx(Size() == 1); + assertrx_throw(Size() == 1); return container_[0].Value(); } @@ -186,12 +186,12 @@ double DistanceBetweenJoinedIndexesSameNs::GetValue(IdType rowId, const joins::N overloaded{ [](const JoinPreResult::Values& values) noexcept { return std::cref(values.payloadType); }, Restricted{}([&js](const auto&) noexcept { return std::cref(js.rightNs_->payloadType_); })}, - js.PreResult().preselectedPayload); + js.PreResult().payload); const ConstPayload pv{pt, SortExpression::getJoinedValue(rowId, joinResults, joinedSelectors, nsIdx)}; TagsMatcher tm = std::visit(overloaded{[](const JoinPreResult::Values& values) noexcept { return std::cref(values.tagsMatcher); }, Restricted{}( [&js](const auto&) noexcept { return std::cref(js.rightNs_->tagsMatcher_); })}, - js.PreResult().preselectedPayload); + js.PreResult().payload); VariantArray values1; if (index1 == IndexValueType::SetByJsonPath) { pv.GetByJsonPath(column1, tm, values1, KeyValueType::Undefined{}); @@ -209,9 +209,9 @@ double DistanceBetweenJoinedIndexesSameNs::GetValue(IdType rowId, const joins::N void SortExpression::openBracketBeforeLastAppended() { const size_t pos = LastAppendedElement(); - assertrx(activeBrackets_.empty() || activeBrackets_.back() < pos); + assertrx_throw(activeBrackets_.empty() || activeBrackets_.back() < pos); for (unsigned i : activeBrackets_) { - assertrx(i < container_.size()); + assertrx_throw(i < container_.size()); container_[i].Append(); } const ArithmeticOpType op = container_[pos].operation.op; @@ -226,18 +226,18 @@ struct ParseIndexNameResult { std::string name; }; +constexpr static estl::Charset kIndexNameSyms{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', + 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', + 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', '.', '+', '"'}; + template static ParseIndexNameResult parseIndexName(std::string_view& expr, const std::vector& joinedSelectors, std::string_view fullExpr) { - static const fast_hash_set allowedSymbolsInIndexName{ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', - 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', '.', '+', '"'}; - auto pos = expr.data(); const auto end = expr.data() + expr.size(); auto joinedSelectorIt = joinedSelectors.cend(); bool joinedFieldInQuotes = false; - while (pos != end && *pos != '.' && allowedSymbolsInIndexName.find(*pos) != allowedSymbolsInIndexName.end()) ++pos; + while (pos != end && *pos != '.' && kIndexNameSyms.test(*pos)) ++pos; if (pos != end && *pos == '.') { std::string_view namespaceName = {expr.data(), static_cast(pos - expr.data())}; @@ -261,7 +261,7 @@ static ParseIndexNameResult parseIndexName(std::string_view& expr, const std: joinedFieldInQuotes = false; } } - while (pos != end && allowedSymbolsInIndexName.find(*pos) != allowedSymbolsInIndexName.end()) ++pos; + while (pos != end && kIndexNameSyms.test(*pos)) ++pos; std::string_view name{expr.data(), static_cast(pos - expr.data())}; if (name.empty()) { throwParseError(fullExpr, pos, "Expected index or function name."); @@ -544,8 +544,8 @@ template SortExpression SortExpression::Parse(std::string_view, const std::vecto double SortExpression::calculate(const_iterator it, const_iterator end, IdType rowId, ConstPayload pv, const joins::NamespaceResults* joinedResults, const std::vector& js, uint8_t proc, TagsMatcher& tagsMatcher) { - assertrx(it != end); - assertrx(it->operation.op == OpPlus); + assertrx_throw(it != end); + assertrx_throw(it->operation.op == OpPlus); double result = 0.0; for (; it != end; ++it) { double value = it->Visit( @@ -700,7 +700,7 @@ std::string SortExpression::Dump() const { } void SortExpression::dump(const_iterator begin, const_iterator end, WrSerializer& ser) { - assertrx(begin->operation.op == OpPlus); + assertrx_throw(begin->operation.op == OpPlus); for (const_iterator it = begin; it != end; ++it) { if (it != begin) { ser << ' '; diff --git a/cpp_src/core/storage/basestorage.cc b/cpp_src/core/storage/basestorage.cc index af07ebc82..91dbe105f 100644 --- a/cpp_src/core/storage/basestorage.cc +++ b/cpp_src/core/storage/basestorage.cc @@ -33,7 +33,9 @@ Error BaseStorage::Open(const std::string& path, const StorageOpts& opts) { info_->repaired = true; lck.unlock(); logPrintf(LogWarning, "Calling repair for '%s'", path); - Repair(path); + if (auto err = Repair(path); !err.ok()) { + logPrintf(LogError, "Rapir error: %s", err.what()); + } } else { lck.unlock(); } diff --git a/cpp_src/core/transaction/localtransaction.h b/cpp_src/core/transaction/localtransaction.h index d35236f39..846d2efc6 100644 --- a/cpp_src/core/transaction/localtransaction.h +++ b/cpp_src/core/transaction/localtransaction.h @@ -8,9 +8,9 @@ namespace reindexer { class LocalTransaction { public: - LocalTransaction(const std::string &nsName, const PayloadType &pt, const TagsMatcher &tm, const FieldsSet &pf, + LocalTransaction(NamespaceName nsName, const PayloadType &pt, const TagsMatcher &tm, const FieldsSet &pf, std::shared_ptr schema, lsn_t lsn) - : data_(std::make_unique(nsName, lsn, Transaction::ClockT::now(), pt, tm, pf, std::move(schema))), + : data_(std::make_unique(std::move(nsName), lsn, Transaction::ClockT::now(), pt, tm, pf, std::move(schema))), tx_(std::make_unique()) {} LocalTransaction(Error err) : err_(std::move(err)) {} @@ -38,7 +38,7 @@ class LocalTransaction { assertrx(data_); return data_->lsn; } - const std::string &GetNsName() const noexcept { + std::string_view GetNsName() const noexcept { assertrx(data_); return data_->nsName; } diff --git a/cpp_src/core/transaction/proxiedtransaction.cc b/cpp_src/core/transaction/proxiedtransaction.cc index df25a16ce..f42d2e5e3 100644 --- a/cpp_src/core/transaction/proxiedtransaction.cc +++ b/cpp_src/core/transaction/proxiedtransaction.cc @@ -53,7 +53,10 @@ Error ProxiedTransaction::Modify(Item &&item, ItemModifyMode mode, lsn_t lsn) { if (clientItem.impl_->tagsMatcher().isUpdated()) { // Disable async logic for tm updates - next items may depend on this - asyncData_.AwaitAsyncRequests(); + auto err = asyncData_.AwaitAsyncRequests(); + if (!err.ok()) { + return err; + } std::unique_lock lck(mtx_); itemCache_.isValid = false; @@ -109,11 +112,12 @@ Error ProxiedTransaction::SetTagsMatcher(TagsMatcher &&tm, lsn_t lsn) { } void ProxiedTransaction::Rollback(int serverId, const RdxContext &ctx) { - asyncData_.AwaitAsyncRequests(); + auto err = asyncData_.AwaitAsyncRequests(); + (void)err; // ignore; Error does not matter here if (tx_.rx_) { - client::QueryResults clientResults; const auto _ctx = client::InternalRdxContext().WithLSN(ctx.GetOriginLSN()).WithEmmiterServerId(serverId); - tx_.rx_->RollBackTransaction(tx_, _ctx); + err = tx_.rx_->RollBackTransaction(tx_, _ctx); + (void)err; // ignore; Error does not matter here } } diff --git a/cpp_src/core/transaction/proxiedtransaction.h b/cpp_src/core/transaction/proxiedtransaction.h index d96e969ae..1ac217231 100644 --- a/cpp_src/core/transaction/proxiedtransaction.h +++ b/cpp_src/core/transaction/proxiedtransaction.h @@ -32,7 +32,10 @@ class ProxiedTransaction { void AddNewAsyncRequest(); void OnAsyncRequestDone(const Error &e) noexcept; Error AwaitAsyncRequests() noexcept; - ~AsyncData() { AwaitAsyncRequests(); } + ~AsyncData() { + auto err = AwaitAsyncRequests(); + (void)err; // ignore + } private: std::mutex &mtx_; diff --git a/cpp_src/core/transaction/sharedtransactiondata.h b/cpp_src/core/transaction/sharedtransactiondata.h index 6558c0eaf..23cb0f74b 100644 --- a/cpp_src/core/transaction/sharedtransactiondata.h +++ b/cpp_src/core/transaction/sharedtransactiondata.h @@ -1,6 +1,7 @@ #pragma once #include "core/cjson/tagsmatcher.h" +#include "core/namespace/namespacename.h" #include "core/payload/fieldsset.h" #include "core/schema.h" #include "transaction.h" @@ -11,8 +12,8 @@ class ItemImpl; class SharedTransactionData { public: - SharedTransactionData(std::string _nsName, lsn_t _lsn, Transaction::ClockT::time_point _startTime, const PayloadType &pt, - const TagsMatcher &tm, const FieldsSet &pf, std::shared_ptr schema) + SharedTransactionData(NamespaceName &&_nsName, lsn_t _lsn, Transaction::ClockT::time_point _startTime, + const PayloadType &pt, const TagsMatcher &tm, const FieldsSet &pf, std::shared_ptr schema) : nsName(std::move(_nsName)), lsn(_lsn), startTime(_startTime), @@ -28,7 +29,7 @@ class SharedTransactionData { const FieldsSet &GetPKFileds() const noexcept { return pkFields_; } std::shared_ptr GetSchema() const noexcept { return schema_; } - const std::string nsName; + const NamespaceName nsName; const lsn_t lsn; const Transaction::TimepointT startTime; diff --git a/cpp_src/core/transaction/transaction.cc b/cpp_src/core/transaction/transaction.cc index 0e3f33a5e..009cd6d24 100644 --- a/cpp_src/core/transaction/transaction.cc +++ b/cpp_src/core/transaction/transaction.cc @@ -13,7 +13,7 @@ Transaction::~Transaction() = default; Transaction::Transaction(Transaction &&) noexcept = default; Transaction &Transaction::operator=(Transaction &&) noexcept = default; -const std::string &Transaction::GetNsName() const noexcept { +std::string_view Transaction::GetNsName() const noexcept { static const std::string empty; if (impl_) { return impl_->GetNsName(); diff --git a/cpp_src/core/transaction/transaction.h b/cpp_src/core/transaction/transaction.h index 53a7a974a..44929ae16 100644 --- a/cpp_src/core/transaction/transaction.h +++ b/cpp_src/core/transaction/transaction.h @@ -60,7 +60,7 @@ class Transaction { Error Status() const noexcept; int GetShardID() const noexcept; - const std::string &GetNsName() const noexcept; + std::string_view GetNsName() const noexcept; bool IsTagsUpdated() const noexcept; TimepointT GetStartTime() const noexcept; diff --git a/cpp_src/core/transaction/transactionimpl.cc b/cpp_src/core/transaction/transactionimpl.cc index 103b9257f..fcbbefcd7 100644 --- a/cpp_src/core/transaction/transactionimpl.cc +++ b/cpp_src/core/transaction/transactionimpl.cc @@ -1,8 +1,6 @@ #include "transactionimpl.h" -#include "client/reindexerimpl.h" #include "cluster/sharding/sharding.h" #include "core/reindexer_impl/reindexerimpl.h" -#include "tools/clusterproxyloghelper.h" namespace reindexer { @@ -239,9 +237,11 @@ LocalTransaction TransactionImpl::Transform(TransactionImpl &tx) { return LocalTransaction(Error(errNotValid, "Non-local transaction")); } -void TransactionImpl::updateShardIdIfNecessary(int shardId) { +void TransactionImpl::updateShardIdIfNecessary(int shardId, const Variant &curShardKey) { if ((shardId_ != ShardingKeyType::NotSetShard) && (shardId != shardId_)) { - throw Error(errLogic, "Transaction query to a different shard: %d (%d is expected)", shardId, shardId_); + throw Error(errLogic, + "Transaction query to a different shard: %d (%d is expected); First tx shard key - %s, current tx shard key - %s", + shardId, shardId_, firstShardKey_.Dump(), curShardKey.Dump()); } if (shardId_ == ShardingKeyType::NotSetShard) { shardId_ = shardId; @@ -267,13 +267,16 @@ void TransactionImpl::updateShardIdIfNecessary(int shardId) { void TransactionImpl::lazyInit(const Query &q) { if (shardingRouter_) { - const auto ids = shardingRouter_.GetShardId(q); + const auto [ids, shardKey] = shardingRouter_.GetShardIdKeyPair(q); + if (firstShardKey_.IsNullValue()) { + firstShardKey_ = shardKey; + } if (ids.size() != 1) { Error status(errLogic, "Transaction query must correspond to exactly one shard (%d corresponding shards found)", ids.size()); status_ = status; throw status; } - updateShardIdIfNecessary(ids.front()); + updateShardIdIfNecessary(ids.front(), shardKey); } else { initProxiedTxIfRequired(); } @@ -301,7 +304,11 @@ void TransactionImpl::initProxiedTxIfRequired() { void TransactionImpl::lazyInit(const Item &item) { if (shardingRouter_) { - updateShardIdIfNecessary(shardingRouter_.GetShardId(data_->nsName, item)); + const auto [id, shardKey] = shardingRouter_.GetShardIdKeyPair(data_->nsName, item); + if (firstShardKey_.IsNullValue()) { + firstShardKey_ = shardKey; + } + updateShardIdIfNecessary(id, shardKey); } else { initProxiedTxIfRequired(); } diff --git a/cpp_src/core/transaction/transactionimpl.h b/cpp_src/core/transaction/transactionimpl.h index afba212c4..14ac1c80d 100644 --- a/cpp_src/core/transaction/transactionimpl.h +++ b/cpp_src/core/transaction/transactionimpl.h @@ -1,6 +1,5 @@ #pragma once -#include #include "client/reindexer.h" #include "cluster/sharding/locatorserviceadapter.h" #include "localtransaction.h" @@ -32,7 +31,7 @@ class TransactionImpl { Error Status() const noexcept; int GetShardID() const noexcept; - const std::string &GetNsName() const noexcept { return data_->nsName; } + std::string_view GetNsName() const noexcept { return data_->nsName; } bool IsTagsUpdated() const noexcept; Transaction::TimepointT GetStartTime() const noexcept { return data_->startTime; } void SetShardingRouter(sharding::LocatorServiceAdapter shardingRouter); @@ -47,7 +46,7 @@ class TransactionImpl { using TxStepsPtr = std::unique_ptr; using RxClientT = client::Reindexer; - void updateShardIdIfNecessary(int shardId); + void updateShardIdIfNecessary(int shardId, const Variant &curShardKey); void lazyInit(const Item &item); void lazyInit(const Query &q); void lazyInit(); @@ -61,6 +60,7 @@ class TransactionImpl { std::variant tx_; int shardId_ = ShardingKeyType::NotSetShard; Error status_; + Variant firstShardKey_; }; } // namespace reindexer diff --git a/cpp_src/core/type_consts.h b/cpp_src/core/type_consts.h index c1b1e4be8..a6183d8af 100644 --- a/cpp_src/core/type_consts.h +++ b/cpp_src/core/type_consts.h @@ -14,6 +14,8 @@ typedef enum TagType { TAG_UUID = 8, } TagType; +static const uint8_t kMaxTagType = TAG_UUID; + typedef enum IndexType { IndexStrHash = 0, IndexStrBTree = 1, @@ -397,6 +399,11 @@ enum SourceId { NotSet = -1 }; static const char kSerialPrefix[] = "_SERIAL_"; +static const uint32_t kMaxStreamsPerSub = 32; +static const int kSubscribersConfigFormatVersion = 1; +static const int kMinSubscribersConfigFormatVersion = 1; +static const int kEventSerializationFormatVersion = 1; + // REINDEX_WITH_V3_FOLLOWERS enum SubscriptionOpt { kSubscriptionOptIncrementSubscription = 1 << 0, diff --git a/cpp_src/coroutine/channel.h b/cpp_src/coroutine/channel.h index d2ab913e2..436cca11c 100644 --- a/cpp_src/coroutine/channel.h +++ b/cpp_src/coroutine/channel.h @@ -3,6 +3,8 @@ #include "coroutine.h" #include "estl/h_vector.h" +#include + namespace reindexer { namespace coroutine { @@ -29,7 +31,7 @@ class channel { /// @param obj - Object to push template void push(U &&obj) { - assertrx(current()); // For now channels should not be used from main routine dew to current resume/suspend logic + assertrx(current()); // For now channels should not be used from main routine dew to current resume/suspend logic bool await = false; while (full() || closed_) { if (closed_) { @@ -62,7 +64,7 @@ class channel { /// writers. /// @return Pair of value and flag. Flag shows if it's actual value from channel (true) or default constructed one (false) std::pair pop() { - assertrx(current()); // For now channels should not be used from main routine dew to current resume/suspend logic + assertrx(current()); // For now channels should not be used from main routine dew to current resume/suspend logic bool await = false; while (empty() && !closed_) { if (!await) { diff --git a/cpp_src/coroutine/coroutine.cc b/cpp_src/coroutine/coroutine.cc index f9b1bf324..ec68821af 100644 --- a/cpp_src/coroutine/coroutine.cc +++ b/cpp_src/coroutine/coroutine.cc @@ -2,6 +2,7 @@ #include #include #include +#include #include "tools/errors.h" #include "tools/clock.h" diff --git a/cpp_src/doc/CMakeLists.txt b/cpp_src/doc/CMakeLists.txt index 359b8c725..2a4daa3dd 100644 --- a/cpp_src/doc/CMakeLists.txt +++ b/cpp_src/doc/CMakeLists.txt @@ -14,12 +14,12 @@ if(DOXYGEN_FOUND) COMMAND ${python3} ${Dox2html} ${PROJECT_SOURCE_DIR}/Doxyfile-mcss COMMENT "Generating Reindexer documentation with Doxygen and Dox2html" ) - else () + else() set(doxyfile ${PROJECT_SOURCE_DIR}/Doxyfile) add_custom_target(doc WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND sh -c "DOXYGEN_OUTPUT_DIRECTORY=${PROJECT_BINARY_DIR} ${DOXYGEN_EXECUTABLE} ${doxyfile}" COMMENT "Generating Reindexer documentation with Doxygen" ) - endif () + endif() endif() diff --git a/cpp_src/estl/charset.h b/cpp_src/estl/charset.h new file mode 100644 index 000000000..b10cf80ef --- /dev/null +++ b/cpp_src/estl/charset.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +namespace reindexer::estl { + +class Charset { +public: + constexpr Charset(std::initializer_list list) { + for (const auto& c : list) { + set(c); + } + } + constexpr bool test(uint8_t pos) const noexcept { return getword(pos) & maskbit(pos); } + constexpr Charset& set(uint8_t pos, bool val = true) noexcept { + if (val) { + this->getword(pos) |= maskbit(pos); + } else { + this->getword(pos) &= ~maskbit(pos); + } + return *this; + } + constexpr static size_t max_values_count() noexcept { return kWordsCount * kBitsPerWord; } + +private: + using WordT = uint64_t; + constexpr static size_t kBitsPerWord = 64; + constexpr static size_t kWordsCount = 4; + + static constexpr uint8_t whichword(uint8_t pos) noexcept { return pos / kBitsPerWord; } + static constexpr WordT maskbit(uint8_t pos) noexcept { return WordT(1) << whichbit(pos); } + static constexpr size_t whichbit(uint8_t pos) noexcept { return pos % kBitsPerWord; } + constexpr WordT getword(uint8_t pos) const noexcept { return set_[whichword(pos)]; } + constexpr WordT& getword(uint8_t pos) noexcept { return set_[whichword(pos)]; } + + WordT set_[kWordsCount] = {0}; +}; + +static_assert(std::numeric_limits::max() - std::numeric_limits::min() + 1 == Charset::max_values_count(), + "Expecting max uint8_t range of [0, 255] for the simplicity"); + +} // namespace reindexer::estl diff --git a/cpp_src/estl/chunk.h b/cpp_src/estl/chunk.h index 6c8db6b1d..56c65beb3 100644 --- a/cpp_src/estl/chunk.h +++ b/cpp_src/estl/chunk.h @@ -11,6 +11,7 @@ class chunk { public: chunk() noexcept : data_(nullptr), len_(0), offset_(0), cap_(0) {} chunk(uint8_t *data, size_t len, size_t cap, size_t offset = 0) noexcept : data_(data), len_(len), offset_(offset), cap_(cap) {} + explicit chunk(size_t cap) : chunk(new uint8_t[cap], 0, cap, 0) {} ~chunk() { delete[] data_; } chunk(const chunk &) = delete; chunk &operator=(const chunk &) = delete; @@ -73,6 +74,7 @@ class chunk { delete[] data_; data_ = newdata; } + explicit operator std::string_view() const noexcept { return std::string_view(reinterpret_cast(data()), size()); } private: void append_impl(std::string_view data, size_t newCapacity) { diff --git a/cpp_src/estl/chunk_buf.h b/cpp_src/estl/chunk_buf.h index daed5c466..2e952f32b 100644 --- a/cpp_src/estl/chunk_buf.h +++ b/cpp_src/estl/chunk_buf.h @@ -7,6 +7,7 @@ #include #include "chunk.h" #include "span.h" +#include "tools/assertrx.h" #include "tools/errors.h" namespace reindexer { @@ -25,6 +26,8 @@ class chain_buf { data_size_ += ch.size(); ring_[head_] = std::move(ch); head_ = new_head; + size_.fetch_add(1, std::memory_order_acq_rel); + assertrx_dbg(size_atomic() == size_impl()); } } void write(std::string_view sv) { @@ -32,7 +35,7 @@ class chain_buf { chunk.append(sv); write(std::move(chunk)); } - span tail() { + span tail() noexcept { std::lock_guard lck(mtx_); size_t cnt = ((tail_ > head_) ? ring_.size() : head_) - tail_; return span(ring_.data() + tail_, cnt); @@ -48,18 +51,29 @@ class chain_buf { cur.shift(nread); break; } + size_.fetch_sub(1, std::memory_order_acq_rel); nread -= cur.size(); - cur.clear(); - if (free_.size() < ring_.size() && cur.capacity() < 0x10000) - free_.push_back(std::move(cur)); - else - cur = chunk(); + recycle(std::move(cur)); tail_ = (tail_ + 1) % ring_.size(); } + assertrx_dbg(size_atomic() == size_impl()); } - chunk get_chunk() { + void erase_chunks(size_t count) { std::lock_guard lck(mtx_); + const auto erase_count = std::min(count, size_impl()); + for (count = erase_count; count > 0; --count) { + assertrx(head_ != tail_); + chunk &cur = ring_[tail_]; + data_size_ -= cur.size(); + recycle(std::move(cur)); + tail_ = (tail_ + 1) % ring_.size(); + } + size_.fetch_sub(erase_count, std::memory_order_acq_rel); + assertrx_dbg(size_atomic() == size_impl()); + } + chunk get_chunk() noexcept { chunk ret; + std::lock_guard lck(mtx_); if (free_.size()) { ret = std::move(free_.back()); free_.pop_back(); @@ -67,29 +81,44 @@ class chain_buf { return ret; } - size_t size() { + size_t size() const noexcept { std::lock_guard lck(mtx_); - return (head_ - tail_ + ring_.size()) % ring_.size(); + return size_impl(); } - size_t data_size() { + size_t size_atomic() const noexcept { return size_.load(std::memory_order_acquire); } + + size_t data_size() const noexcept { std::lock_guard lck(mtx_); return data_size_; } - size_t capacity() { + size_t capacity() const noexcept { std::lock_guard lck(mtx_); return ring_.size() - 1; } - void clear() { + void clear() noexcept { std::lock_guard lck(mtx_); head_ = tail_ = data_size_ = 0; + size_.store(0, std::memory_order_release); + assertrx_dbg(size_atomic() == size_impl()); + } + +private: + size_t size_impl() const noexcept { return (head_ - tail_ + ring_.size()) % ring_.size(); } + void recycle(chunk &&ch) { + if (free_.size() < ring_.size() && ch.capacity() < 0x10000) { + ch.clear(); + free_.emplace_back(std::move(ch)); + } else { + ch = chunk(); + } } -protected: size_t head_ = 0, tail_ = 0, data_size_ = 0; + std::atomic size_ = {0}; std::vector ring_, free_; - Mutex mtx_; + mutable Mutex mtx_; }; } // namespace reindexer diff --git a/cpp_src/estl/contexted_locks.h b/cpp_src/estl/contexted_locks.h index 5526bb4e7..12c09423b 100644 --- a/cpp_src/estl/contexted_locks.h +++ b/cpp_src/estl/contexted_locks.h @@ -16,7 +16,7 @@ namespace reindexer { constexpr milliseconds kDefaultCondChkTime = milliseconds(50); template -class contexted_unique_lock { +class [[nodiscard]] contexted_unique_lock { public: using MutexType = _Mutex; @@ -109,7 +109,7 @@ class contexted_unique_lock { }; template -class contexted_shared_lock { +class [[nodiscard]] contexted_shared_lock { public: using MutexType = _Mutex; diff --git a/cpp_src/estl/flat_str_map.h b/cpp_src/estl/flat_str_map.h index 8fafbc01e..8824add5a 100644 --- a/cpp_src/estl/flat_str_map.h +++ b/cpp_src/estl/flat_str_map.h @@ -38,36 +38,31 @@ class flat_str_map { bool operator()(string_view_t lhs, size_t rhs) const { return lhs == buf_->get(rhs); } bool operator()(size_t lhs, string_view_t rhs) const { return rhs == buf_->get(lhs); } - protected: + private: const holder_t *buf_; }; struct hash_flat_str_map { using is_transparent = void; hash_flat_str_map(const holder_t *buf) noexcept : buf_(buf) {} - size_t operator()(string_view_t hs) const { return _Hash_bytes(hs.data(), hs.length()); } + size_t operator()(string_view_t hs) const noexcept { return _Hash_bytes(hs.data(), hs.length()); } size_t operator()(size_t hs) const { return operator()(buf_->get(hs)); } - protected: + private: const holder_t *buf_; }; + using hash_map = tsl::hopscotch_map>, 30, false, tsl::mod_growth_policy>>; public: - flat_str_map() : holder_(new holder_t), map_(new hash_map(16, hash_flat_str_map(holder_.get()), equal_flat_str_map(holder_.get()))) {} + flat_str_map() + : holder_(std::make_unique()), + map_(std::make_unique(16, hash_flat_str_map(holder_.get()), equal_flat_str_map(holder_.get()))) {} flat_str_map(const flat_str_map &other) = delete; flat_str_map &operator=(const flat_str_map &other) = delete; - - flat_str_map(flat_str_map &&rhs) noexcept : holder_(std::move(rhs.holder_)), map_(std::move(rhs.map_)), multi_(std::move(rhs.multi_)) {} - flat_str_map &operator=(flat_str_map &&rhs) noexcept { - if (&rhs != this) { - holder_ = std::move(rhs.buf_); - map_ = std::move(rhs.map_); - multi_ = std::move(rhs.multi_); - } - return *this; - } + flat_str_map(flat_str_map &&rhs) noexcept = default; + flat_str_map &operator=(flat_str_map &&rhs) noexcept = default; template class value_type : public std::pair { @@ -82,12 +77,12 @@ class flat_str_map { friend class flat_str_map; public: - base_iterator(map_iterator it, map_type *m, int multi_idx) : it_(it), m_(m), multi_idx_(multi_idx) {} - base_iterator(map_iterator it, map_type *m) + base_iterator(map_iterator it, map_type *m, int multi_idx) noexcept : it_(it), m_(m), multi_idx_(multi_idx) {} + base_iterator(map_iterator it, map_type *m) noexcept : it_(it), m_(m), multi_idx_((Multi && it_ != m_->map_->end() && it_->second.IsMultiValue()) ? it_->second.GetWordID() : -1) {} base_iterator(const base_iterator &other) : it_(other.it_), m_(other.m_), multi_idx_(other.multi_idx_) {} // NOLINTNEXTLINE(bugprone-unhandled-self-assignment) - base_iterator &operator=(const base_iterator &other) { + base_iterator &operator=(const base_iterator &other) noexcept { it_ = other.it_; m_ = other.m_; multi_idx_ = other.multi_idx_; @@ -114,7 +109,7 @@ class flat_str_map { return value_type(m_->holder_->get(it_->first), it_->second); } - base_iterator &operator++() { + base_iterator &operator++() noexcept { if constexpr (Multi) { if (multi_idx_ != -1) { multi_idx_ = m_->multi_[multi_idx_].next; @@ -129,27 +124,27 @@ class flat_str_map { } return *this; } - base_iterator &operator--() { + base_iterator &operator--() noexcept { static_assert(Multi, "Sorry, flat_std_multimap::iterator::operator-- () is not implemented"); --it_; return *this; } - base_iterator operator++(int) { + base_iterator operator++(int) noexcept { base_iterator ret = *this; ++(*this); return ret; } - base_iterator operator--(int) { + base_iterator operator--(int) noexcept { base_iterator ret = *this; --(*this); return ret; } template - bool operator!=(const it2 &rhs) const { + bool operator!=(const it2 &rhs) const noexcept { return it_ != rhs.it_ || multi_idx_ != rhs.multi_idx_; } template - bool operator==(const it2 &rhs) const { + bool operator==(const it2 &rhs) const noexcept { return it_ == rhs.it_ && multi_idx_ == rhs.multi_idx_; } @@ -162,22 +157,22 @@ class flat_str_map { using iterator = base_iterator>; using const_iterator = base_iterator>; - iterator begin() { return iterator(map_->begin(), this); } - iterator end() { return iterator(map_->end(), this); } - const_iterator begin() const { return const_iterator(map_->begin(), this); } - const_iterator end() const { return const_iterator(map_->end(), this); } + iterator begin() noexcept { return iterator(map_->begin(), this); } + iterator end() noexcept { return iterator(map_->end(), this); } + const_iterator begin() const noexcept { return const_iterator(map_->begin(), this); } + const_iterator end() const noexcept { return const_iterator(map_->end(), this); } - const_iterator find(string_view_t str) const { return const_iterator(map_->find(str), this); } - iterator find(string_view_t str) { return iterator(map_->find(str), this); } + const_iterator find(string_view_t str) const noexcept { return const_iterator(map_->find(str), this); } + iterator find(string_view_t str) noexcept { return iterator(map_->find(str), this); } - std::pair equal_range(string_view_t str) { + std::pair equal_range(string_view_t str) noexcept { auto it = map_->find(str); auto it2 = it; if (it2 != map_->end()) ++it2; return {iterator(it, this), iterator(it2, this)}; } - std::pair equal_range(string_view_t str) const { + std::pair equal_range(string_view_t str) const noexcept { auto it = map_->find(str); auto it2 = it; if (it2 != map_->end()) ++it2; @@ -186,18 +181,19 @@ class flat_str_map { std::pair insert(string_view_t str, const V &v) { size_t pos = holder_->put(str); - auto res = map_->emplace(pos, v); + const auto h = hash_flat_str_map(holder_.get())(str); + auto res = map_->try_emplace_prehashed(h, pos, v); int multi_pos = -1; if (!res.second) { holder_->resize(pos); if constexpr (Multi) { multi_pos = multi_.size(); if (!(res.first->second.IsMultiValue())) { - multi_.push_back({res.first->second, multi_pos + 1}); - multi_.push_back({v, -1}); + multi_.emplace_back(res.first->second, multi_pos + 1); + multi_.emplace_back(v, -1); } else { int old_multi_pos = res.first->second.GetWordID(); - multi_.push_back({v, old_multi_pos}); + multi_.emplace_back(v, old_multi_pos); } res.first->second.SetWordID(multi_pos); res.first->second.SetMultiValueFlag(true); @@ -205,7 +201,7 @@ class flat_str_map { } return {iterator(res.first, this, multi_pos), res.second}; } - void clear() { + void clear() noexcept { holder_->clear(); map_->clear(); if constexpr (Multi) { @@ -223,8 +219,10 @@ class flat_str_map { } } - size_t size() const { return map_->size(); } - size_t heap_size() const { return holder_->capacity() + map_->allocated_mem_size() + multi_.capacity() * sizeof(multi_node); } + size_t size() const noexcept { return map_->size(); } + size_t bucket_count() const noexcept { return map_->bucket_count(); } + size_t str_size() const noexcept { return holder_->size(); } + size_t heap_size() const noexcept { return holder_->capacity() + map_->allocated_mem_size() + multi_.capacity() * sizeof(multi_node); } void shrink_to_fit() { holder_->shrink_to_fit(); @@ -239,6 +237,9 @@ class flat_str_map { // Underlying map container std::unique_ptr map_; struct multi_node { + multi_node(const V &v, int n) : val(v), next(n) {} + multi_node(V &&v, int n) noexcept : val(std::move(v)), next(n) {} + V val; int next; }; diff --git a/cpp_src/estl/intrusive_ptr.h b/cpp_src/estl/intrusive_ptr.h index 35a7e9a35..0fd9dfbad 100644 --- a/cpp_src/estl/intrusive_ptr.h +++ b/cpp_src/estl/intrusive_ptr.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include "tools/assertrx.h" @@ -14,7 +13,8 @@ class intrusive_ptr { public: typedef T element_type; - intrusive_ptr() noexcept = default; + constexpr intrusive_ptr() noexcept = default; + constexpr intrusive_ptr(std::nullptr_t) noexcept {} intrusive_ptr(T *p, bool add_ref = true) noexcept : px(p) { if (px != 0 && add_ref) intrusive_ptr_add_ref(px); diff --git a/cpp_src/estl/span.h b/cpp_src/estl/span.h index d16134e89..3896d3aed 100644 --- a/cpp_src/estl/span.h +++ b/cpp_src/estl/span.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include "tools/assertrx.h" #include "trivial_reverse_iterator.h" @@ -36,11 +36,27 @@ class span { return *this; } - // FIXME: const override - template - constexpr span(const Container& other) noexcept : data_(const_cast(other.data())), size_(other.size()) {} - - explicit constexpr span(const T* str, size_type len) noexcept : data_(const_cast(str)), size_(len) {} + template