diff --git a/Package.swift b/Package.swift index 65fe5ef0..4798b6fe 100644 --- a/Package.swift +++ b/Package.swift @@ -76,7 +76,9 @@ var package = Package( ), .target( name: "ValkeySearch", - dependencies: ["Valkey"], + dependencies: [ + "Valkey" + ], swiftSettings: defaultSwiftSettings ), .target( diff --git a/Sources/Valkey/RESP/RESPRenderableHelpers.swift b/Sources/Valkey/RESP/RESPRenderableHelpers.swift index 0581e3de..b88a0b41 100644 --- a/Sources/Valkey/RESP/RESPRenderableHelpers.swift +++ b/Sources/Valkey/RESP/RESPRenderableHelpers.swift @@ -7,6 +7,7 @@ // import NIOCore +/// Render token if boolean is true @usableFromInline package struct RESPPureToken: RESPRenderable { @usableFromInline @@ -29,6 +30,7 @@ package struct RESPPureToken: RESPRenderable { } } +/// Render token and value if value is not nil @usableFromInline package struct RESPWithToken: RESPRenderable { @usableFromInline @@ -57,6 +59,7 @@ package struct RESPWithToken: RESPRenderable { } } +/// Render array with token preceding each item @usableFromInline package struct RESPArrayWithToken: RESPRenderable { @usableFromInline @@ -82,6 +85,7 @@ package struct RESPArrayWithToken: RESPRenderable { } } +/// Render array item count and then array @usableFromInline package struct RESPArrayWithCount: RESPRenderable { @usableFromInline @@ -101,3 +105,81 @@ package struct RESPArrayWithCount: RESPRenderable { self.array.encode(into: &commandEncoder) } } + +/// Render number of resp parameters array generates and then array +@usableFromInline +package struct RESPArrayWithParameterCount: RESPRenderable { + @usableFromInline + let array: [Element] + + @inlinable + package init(_ array: [Element]) { + self.array = array + } + @inlinable + package var respEntries: Int { + self.array.respEntries + 1 + } + @inlinable + package func encode(into commandEncoder: inout ValkeyCommandEncoder) { + self.array.respEntries.encode(into: &commandEncoder) + self.array.encode(into: &commandEncoder) + } +} + +/// Render token, count of items in array and array, but only if array is non-empty +@usableFromInline +package struct RESPArrayWithTokenAndCount: RESPRenderable { + @usableFromInline + let array: [Element] + @usableFromInline + let token: String + + @inlinable + package init(_ token: String, _ array: [Element]) { + self.token = token + self.array = array + } + + @inlinable + package var respEntries: Int { + guard self.array.count > 0 else { return 0 } + return self.array.respEntries + 2 + } + @inlinable + package func encode(into commandEncoder: inout ValkeyCommandEncoder) { + guard self.array.count > 0 else { return } + self.token.encode(into: &commandEncoder) + self.array.count.encode(into: &commandEncoder) + self.array.encode(into: &commandEncoder) + } +} + +/// Render token, count of parameters generated by array and array, but only if array is non-empty +@usableFromInline +package struct RESPArrayWithTokenAndParameterCount: RESPRenderable { + @usableFromInline + let array: [Element] + @usableFromInline + let token: String + + @inlinable + package init(_ token: String, _ array: [Element]) { + self.token = token + self.array = array + } + @inlinable + package var respEntries: Int { + let respEntries = self.array.respEntries + guard respEntries > 0 else { return 0 } + return respEntries + 2 + } + @inlinable + package func encode(into commandEncoder: inout ValkeyCommandEncoder) { + let respEntries = self.array.respEntries + guard respEntries > 0 else { return } + self.token.encode(into: &commandEncoder) + respEntries.encode(into: &commandEncoder) + self.array.encode(into: &commandEncoder) + } +} diff --git a/Sources/ValkeySearch/SearchCommands.swift b/Sources/ValkeySearch/SearchCommands.swift index 01ac3658..cfaaa46c 100644 --- a/Sources/ValkeySearch/SearchCommands.swift +++ b/Sources/ValkeySearch/SearchCommands.swift @@ -21,103 +21,78 @@ public enum FT { /// Performs a search of the specified index. The keys which match the query expression are subjected to further processing as specified @_documentation(visibility: internal) public struct AGGREGATE: ValkeyCommand { - public struct LoadItemsAlias: RESPRenderable, Sendable, Hashable { - public var property: String - - @inlinable - public init(property: String) { - self.property = property - } - - @inlinable - public var respEntries: Int { - "AS".respEntries + property.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "AS".encode(into: &commandEncoder) - property.encode(into: &commandEncoder) - } - } - public struct LoadItems: RESPRenderable, Sendable, Hashable { - public var identifier: String - public var alias: LoadItemsAlias? - - @inlinable - public init(identifier: String, alias: LoadItemsAlias? = nil) { - self.identifier = identifier - self.alias = alias - } + public enum Load: RESPRenderable, Sendable, Hashable { + case all + case fields([String]) @inlinable public var respEntries: Int { - identifier.respEntries + alias.respEntries + switch self { + case .all: "*".respEntries + case .fields(let fields): RESPArrayWithCount(fields).respEntries + } } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - identifier.encode(into: &commandEncoder) - alias.encode(into: &commandEncoder) + switch self { + case .all: "*".encode(into: &commandEncoder) + case .fields(let fields): RESPArrayWithCount(fields).encode(into: &commandEncoder) + } } } - public struct Load: RESPRenderable, Sendable, Hashable { - public var nargs: Int - public var items: [LoadItems] + public struct Params: RESPRenderable, Sendable, Hashable { + public var name: String + public var value: String @inlinable - public init(nargs: Int, items: [LoadItems]) { - self.nargs = nargs - self.items = items + public init(name: String, value: String) { + self.name = name + self.value = value } @inlinable public var respEntries: Int { - "LOAD".respEntries + nargs.respEntries + items.respEntries + name.respEntries + value.respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "LOAD".encode(into: &commandEncoder) - nargs.encode(into: &commandEncoder) - items.encode(into: &commandEncoder) + name.encode(into: &commandEncoder) + value.encode(into: &commandEncoder) } } - public struct Groupby: RESPRenderable, Sendable, Hashable { - public var nargs: Int - public var groupFields: [String] + public struct Apply: RESPRenderable, Sendable, Hashable { + public var expression: String + public var field: String @inlinable - public init(nargs: Int, groupFields: [String]) { - self.nargs = nargs - self.groupFields = groupFields + public init(expression: String, field: String) { + self.expression = expression + self.field = field } @inlinable public var respEntries: Int { - "GROUPBY".respEntries + nargs.respEntries + groupFields.respEntries + "APPLY".respEntries + expression.respEntries + "AS".respEntries + field.respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "GROUPBY".encode(into: &commandEncoder) - nargs.encode(into: &commandEncoder) - groupFields.encode(into: &commandEncoder) + "APPLY".encode(into: &commandEncoder) + expression.encode(into: &commandEncoder) + "AS".encode(into: &commandEncoder) + field.encode(into: &commandEncoder) } } - public enum ReduceFunction: RESPRenderable, Sendable, Hashable { + public enum GroupbyReduceFunction: RESPRenderable, Sendable, Hashable { case count case countDistinct - case countDistinctish case sum case min case max case avg case stddev - case quantile - case tolist - case firstValue - case randomSample @inlinable public var respEntries: Int { 1 } @@ -127,391 +102,188 @@ public enum FT { switch self { case .count: "COUNT".encode(into: &commandEncoder) case .countDistinct: "COUNT_DISTINCT".encode(into: &commandEncoder) - case .countDistinctish: "COUNT_DISTINCT_ISH".encode(into: &commandEncoder) case .sum: "SUM".encode(into: &commandEncoder) case .min: "MIN".encode(into: &commandEncoder) case .max: "MAX".encode(into: &commandEncoder) case .avg: "AVG".encode(into: &commandEncoder) case .stddev: "STDDEV".encode(into: &commandEncoder) - case .quantile: "QUANTILE".encode(into: &commandEncoder) - case .tolist: "TOLIST".encode(into: &commandEncoder) - case .firstValue: "FIRST_VALUE".encode(into: &commandEncoder) - case .randomSample: "RANDOM_SAMPLE".encode(into: &commandEncoder) } } } - public struct ReduceAlias: RESPRenderable, Sendable, Hashable { - public var identifier: String - - @inlinable - public init(identifier: String) { - self.identifier = identifier - } - - @inlinable - public var respEntries: Int { - "AS".respEntries + identifier.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "AS".encode(into: &commandEncoder) - identifier.encode(into: &commandEncoder) - } - } - public struct Reduce: RESPRenderable, Sendable, Hashable { - public var function: ReduceFunction - public var nargs: Int - public var identifiers: [String] - public var alias: ReduceAlias? + public struct GroupbyReduce: RESPRenderable, Sendable, Hashable { + public var function: GroupbyReduceFunction + public var expressions: [String] + public var alias: String? @inlinable - public init(function: ReduceFunction, nargs: Int, identifiers: [String], alias: ReduceAlias? = nil) { + public init(function: GroupbyReduceFunction, expressions: [String] = [], alias: String? = nil) { self.function = function - self.nargs = nargs - self.identifiers = identifiers + self.expressions = expressions self.alias = alias } @inlinable public var respEntries: Int { - "REDUCE".respEntries + function.respEntries + nargs.respEntries + identifiers.respEntries + alias.respEntries + "REDUCE".respEntries + function.respEntries + RESPArrayWithCount(expressions).respEntries + RESPWithToken("AS", alias).respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { "REDUCE".encode(into: &commandEncoder) function.encode(into: &commandEncoder) - nargs.encode(into: &commandEncoder) - identifiers.encode(into: &commandEncoder) - alias.encode(into: &commandEncoder) - } - } - public struct SortbyMax: RESPRenderable, Sendable, Hashable { - public var num: Int - - @inlinable - public init(num: Int) { - self.num = num - } - - @inlinable - public var respEntries: Int { - "MAX".respEntries + num.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "MAX".encode(into: &commandEncoder) - num.encode(into: &commandEncoder) + RESPArrayWithCount(expressions).encode(into: &commandEncoder) + RESPWithToken("AS", alias).encode(into: &commandEncoder) } } - public struct Sortby: RESPRenderable, Sendable, Hashable { - public var nargs: Int - public var sortParams: [String] - public var max: SortbyMax? - public var withcount: Bool - - @inlinable - public init(nargs: Int, sortParams: [String], max: SortbyMax? = nil, withcount: Bool = false) { - self.nargs = nargs - self.sortParams = sortParams - self.max = max - self.withcount = withcount - } - - @inlinable - public var respEntries: Int { - "SORTBY".respEntries + nargs.respEntries + sortParams.respEntries + max.respEntries - + RESPPureToken("WITHCOUNT", withcount).respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "SORTBY".encode(into: &commandEncoder) - nargs.encode(into: &commandEncoder) - sortParams.encode(into: &commandEncoder) - max.encode(into: &commandEncoder) - RESPPureToken("WITHCOUNT", withcount).encode(into: &commandEncoder) - } - } - public struct Apply: RESPRenderable, Sendable, Hashable { - public var expr: String - public var name: String + public struct Groupby: RESPRenderable, Sendable, Hashable { + public var fields: [String] + public var reduces: [GroupbyReduce] @inlinable - public init(expr: String, name: String) { - self.expr = expr - self.name = name + public init(fields: [String], reduces: [GroupbyReduce] = []) { + self.fields = fields + self.reduces = reduces } @inlinable public var respEntries: Int { - "APPLY".respEntries + expr.respEntries + "AS".respEntries + name.respEntries + "GROUPBY".respEntries + RESPArrayWithCount(fields).respEntries + reduces.respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "APPLY".encode(into: &commandEncoder) - expr.encode(into: &commandEncoder) - "AS".encode(into: &commandEncoder) - name.encode(into: &commandEncoder) + "GROUPBY".encode(into: &commandEncoder) + RESPArrayWithCount(fields).encode(into: &commandEncoder) + reduces.encode(into: &commandEncoder) } } public struct Limit: RESPRenderable, Sendable, Hashable { public var offset: Int - public var num: Int + public var count: Int @inlinable - public init(offset: Int, num: Int) { + public init(offset: Int, count: Int) { self.offset = offset - self.num = num + self.count = count } @inlinable public var respEntries: Int { - "LIMIT".respEntries + offset.respEntries + num.respEntries + "LIMIT".respEntries + offset.respEntries + count.respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { "LIMIT".encode(into: &commandEncoder) offset.encode(into: &commandEncoder) - num.encode(into: &commandEncoder) - } - } - public struct Filter: RESPRenderable, Sendable, Hashable { - public var expr: String - - @inlinable - public init(expr: String) { - self.expr = expr - } - - @inlinable - public var respEntries: Int { - "FILTER".respEntries + expr.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "FILTER".encode(into: &commandEncoder) - expr.encode(into: &commandEncoder) - } - } - public struct WithcursorCount: RESPRenderable, Sendable, Hashable { - public var readSize: Int - - @inlinable - public init(readSize: Int) { - self.readSize = readSize - } - - @inlinable - public var respEntries: Int { - "COUNT".respEntries + readSize.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "COUNT".encode(into: &commandEncoder) - readSize.encode(into: &commandEncoder) - } - } - public struct WithcursorMaxidle: RESPRenderable, Sendable, Hashable { - public var idleTime: Int - - @inlinable - public init(idleTime: Int) { - self.idleTime = idleTime - } - - @inlinable - public var respEntries: Int { - "MAXIDLE".respEntries + idleTime.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "MAXIDLE".encode(into: &commandEncoder) - idleTime.encode(into: &commandEncoder) - } - } - public struct Withcursor: RESPRenderable, Sendable, Hashable { - public var count: WithcursorCount - public var maxidle: WithcursorMaxidle? - - @inlinable - public init(count: WithcursorCount, maxidle: WithcursorMaxidle? = nil) { - self.count = count - self.maxidle = maxidle - } - - @inlinable - public var respEntries: Int { - "WITHCURSOR".respEntries + count.respEntries + maxidle.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "WITHCURSOR".encode(into: &commandEncoder) count.encode(into: &commandEncoder) - maxidle.encode(into: &commandEncoder) } } - public struct Timeout: RESPRenderable, Sendable, Hashable { - public var milliseconds: Int + public enum SortbyExpressionDirection: RESPRenderable, Sendable, Hashable { + case asc + case desc @inlinable - public init(milliseconds: Int) { - self.milliseconds = milliseconds - } - - @inlinable - public var respEntries: Int { - "TIMEOUT".respEntries + milliseconds.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "TIMEOUT".encode(into: &commandEncoder) - milliseconds.encode(into: &commandEncoder) - } - } - public struct ParamsParameters: RESPRenderable, Sendable, Hashable { - public var name: String - public var value: String - - @inlinable - public init(name: String, value: String) { - self.name = name - self.value = value - } - - @inlinable - public var respEntries: Int { - name.respEntries + value.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - name.encode(into: &commandEncoder) - value.encode(into: &commandEncoder) - } - } - public struct Params: RESPRenderable, Sendable, Hashable { - public var nargs: Int - public var parameters: [ParamsParameters] - - @inlinable - public init(nargs: Int, parameters: [ParamsParameters]) { - self.nargs = nargs - self.parameters = parameters - } - - @inlinable - public var respEntries: Int { - "PARAMS".respEntries + nargs.respEntries + parameters.respEntries - } + public var respEntries: Int { 1 } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "PARAMS".encode(into: &commandEncoder) - nargs.encode(into: &commandEncoder) - parameters.encode(into: &commandEncoder) + switch self { + case .asc: "ASC".encode(into: &commandEncoder) + case .desc: "DESC".encode(into: &commandEncoder) + } } } - public struct Scorer: RESPRenderable, Sendable, Hashable { - public var scorer: String + public struct SortbyExpression: RESPRenderable, Sendable, Hashable { + public var expression: String + public var direction: SortbyExpressionDirection? @inlinable - public init(scorer: String) { - self.scorer = scorer + public init(expression: String, direction: SortbyExpressionDirection? = nil) { + self.expression = expression + self.direction = direction } @inlinable public var respEntries: Int { - "SCORER".respEntries + scorer.respEntries + expression.respEntries + direction.respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "SCORER".encode(into: &commandEncoder) - scorer.encode(into: &commandEncoder) + expression.encode(into: &commandEncoder) + direction.encode(into: &commandEncoder) } } - public struct Dialect: RESPRenderable, Sendable, Hashable { - public var dialectVersion: Int + public struct Sortby: RESPRenderable, Sendable, Hashable { + public var expressions: [SortbyExpression] + public var max: Int? @inlinable - public init(dialectVersion: Int) { - self.dialectVersion = dialectVersion + public init(expressions: [SortbyExpression], max: Int? = nil) { + self.expressions = expressions + self.max = max } @inlinable public var respEntries: Int { - "DIALECT".respEntries + dialectVersion.respEntries + "SORTBY".respEntries + RESPArrayWithParameterCount(expressions).respEntries + RESPWithToken("MAX", max).respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "DIALECT".encode(into: &commandEncoder) - dialectVersion.encode(into: &commandEncoder) + "SORTBY".encode(into: &commandEncoder) + RESPArrayWithParameterCount(expressions).encode(into: &commandEncoder) + RESPWithToken("MAX", max).encode(into: &commandEncoder) } } @inlinable public static var name: String { "FT.AGGREGATE" } public var index: ValkeyKey public var query: Query - public var verbatim: Bool + public var dialect: Int? + public var inorder: Bool public var load: Load? + public var params: [Params] + public var slop: Int? + public var timeout: Int? + public var verbatim: Bool + public var applys: [Apply] + public var filters: [String] public var groupbys: [Groupby] - public var reduces: [Reduce] + public var limits: [Limit] public var sortby: Sortby? - public var applys: [Apply] - public var limit: Limit? - public var filters: [Filter] - public var withcursor: Withcursor? - public var timeout: Timeout? - public var params: Params? - public var scorer: Scorer? - public var addscores: Bool - public var dialect: Dialect? @inlinable public init( index: ValkeyKey, query: Query, - verbatim: Bool = false, + dialect: Int? = nil, + inorder: Bool = false, load: Load? = nil, - groupbys: [Groupby] = [], - reduces: [Reduce] = [], - sortby: Sortby? = nil, + params: [Params] = [], + slop: Int? = nil, + timeout: Int? = nil, + verbatim: Bool = false, applys: [Apply] = [], - limit: Limit? = nil, - filters: [Filter] = [], - withcursor: Withcursor? = nil, - timeout: Timeout? = nil, - params: Params? = nil, - scorer: Scorer? = nil, - addscores: Bool = false, - dialect: Dialect? = nil + filters: [String] = [], + groupbys: [Groupby] = [], + limits: [Limit] = [], + sortby: Sortby? = nil ) { self.index = index self.query = query - self.verbatim = verbatim + self.dialect = dialect + self.inorder = inorder self.load = load - self.groupbys = groupbys - self.reduces = reduces - self.sortby = sortby + self.params = params + self.slop = slop + self.timeout = timeout + self.verbatim = verbatim self.applys = applys - self.limit = limit self.filters = filters - self.withcursor = withcursor - self.timeout = timeout - self.params = params - self.scorer = scorer - self.addscores = addscores - self.dialect = dialect + self.groupbys = groupbys + self.limits = limits + self.sortby = sortby } public var keysAffected: CollectionOfOne { .init(index) } @@ -521,20 +293,18 @@ public enum FT { "FT.AGGREGATE", index, RESPRenderableBulkString(query), + RESPWithToken("DIALECT", dialect), + RESPPureToken("INORDER", inorder), + RESPWithToken("LOAD", load), + RESPArrayWithTokenAndParameterCount("PARAMS", params), + RESPWithToken("SLOP", slop), + RESPWithToken("TIMEOUT", timeout), RESPPureToken("VERBATIM", verbatim), - load, - groupbys, - reduces, - sortby, applys, - limit, - filters, - withcursor, - timeout, - params, - scorer, - RESPPureToken("ADDSCORES", addscores), - dialect + RESPArrayWithToken("FILTER", filters), + groupbys, + limits, + sortby ) } } @@ -542,7 +312,7 @@ public enum FT { /// Creates an empty search index and initiates the backfill process @_documentation(visibility: internal) public struct CREATE: ValkeyCommand { - public enum On_Type: RESPRenderable, Sendable, Hashable { + public enum On: RESPRenderable, Sendable, Hashable { case hash case json @@ -557,108 +327,105 @@ public enum FT { } } } - public struct On: RESPRenderable, Sendable, Hashable { - public var type: On_Type - - @inlinable - public init(type: On_Type) { - self.type = type - } + public enum Offsets: RESPRenderable, Sendable, Hashable { + case withoffsets + case nooffsets @inlinable - public var respEntries: Int { - "ON".respEntries + type.respEntries - } + public var respEntries: Int { 1 } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "ON".encode(into: &commandEncoder) - type.encode(into: &commandEncoder) + switch self { + case .withoffsets: "WITHOFFSETS".encode(into: &commandEncoder) + case .nooffsets: "NOOFFSETS".encode(into: &commandEncoder) + } } } - public struct Prefix: RESPRenderable, Sendable, Hashable { - public var count: Int - public var prefixes: [String] - - @inlinable - public init(count: Int, prefixes: [String]) { - self.count = count - self.prefixes = prefixes - } + public enum Stopwords: RESPRenderable, Sendable, Hashable { + case nostopwords + case stopwordsLists([String]) @inlinable public var respEntries: Int { - "PREFIX".respEntries + count.respEntries + prefixes.respEntries + switch self { + case .nostopwords: "NOSTOPWORDS".respEntries + case .stopwordsLists(let stopwordsLists): RESPArrayWithTokenAndCount("STOPWORDS", stopwordsLists).respEntries + } } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "PREFIX".encode(into: &commandEncoder) - count.encode(into: &commandEncoder) - prefixes.encode(into: &commandEncoder) + switch self { + case .nostopwords: "NOSTOPWORDS".encode(into: &commandEncoder) + case .stopwordsLists(let stopwordsLists): RESPArrayWithTokenAndCount("STOPWORDS", stopwordsLists).encode(into: &commandEncoder) + } } } - public struct SchemaFieldsAlias: RESPRenderable, Sendable, Hashable { - public var fieldIdentifier: FieldIdentifier + public struct SchemaFieldTypeTag: RESPRenderable, Sendable, Hashable { + public var separator: String? + public var casesensitive: Bool @inlinable - public init(fieldIdentifier: FieldIdentifier) { - self.fieldIdentifier = fieldIdentifier + public init(separator: String? = nil, casesensitive: Bool = false) { + self.separator = separator + self.casesensitive = casesensitive } @inlinable public var respEntries: Int { - "AS".respEntries + RESPRenderableBulkString(fieldIdentifier).respEntries + "TAG".respEntries + RESPWithToken("SEPARATOR", separator).respEntries + RESPPureToken("CASESENSITIVE", casesensitive).respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "AS".encode(into: &commandEncoder) - RESPRenderableBulkString(fieldIdentifier).encode(into: &commandEncoder) + "TAG".encode(into: &commandEncoder) + RESPWithToken("SEPARATOR", separator).encode(into: &commandEncoder) + RESPPureToken("CASESENSITIVE", casesensitive).encode(into: &commandEncoder) } } - public struct SchemaFieldsFieldTypeTagSeparator: RESPRenderable, Sendable, Hashable { - public var sep: String - - @inlinable - public init(sep: String) { - self.sep = sep - } + public enum SchemaFieldTypeTextSuffixTrie: RESPRenderable, Sendable, Hashable { + case withsuffixtrie + case nosuffixtrie @inlinable - public var respEntries: Int { - "SEPARATOR".respEntries + sep.respEntries - } + public var respEntries: Int { 1 } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "SEPARATOR".encode(into: &commandEncoder) - sep.encode(into: &commandEncoder) + switch self { + case .withsuffixtrie: "WITHSUFFIXTRIE".encode(into: &commandEncoder) + case .nosuffixtrie: "NOSUFFIXTRIE".encode(into: &commandEncoder) + } } } - public struct SchemaFieldsFieldTypeTag: RESPRenderable, Sendable, Hashable { - public var separator: SchemaFieldsFieldTypeTagSeparator? - public var casesensitive: Bool + public struct SchemaFieldTypeText: RESPRenderable, Sendable, Hashable { + public var nostem: Bool + public var suffixTrie: SchemaFieldTypeTextSuffixTrie? + public var weight: String? @inlinable - public init(separator: SchemaFieldsFieldTypeTagSeparator? = nil, casesensitive: Bool = false) { - self.separator = separator - self.casesensitive = casesensitive + public init(nostem: Bool = false, suffixTrie: SchemaFieldTypeTextSuffixTrie? = nil, weight: String? = nil) { + self.nostem = nostem + self.suffixTrie = suffixTrie + self.weight = weight } @inlinable public var respEntries: Int { - "TAG".respEntries + separator.respEntries + RESPPureToken("CASESENSITIVE", casesensitive).respEntries + "TEXT".respEntries + RESPPureToken("NOSTEM", nostem).respEntries + suffixTrie.respEntries + + RESPWithToken("WEIGHT", weight).respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "TAG".encode(into: &commandEncoder) - separator.encode(into: &commandEncoder) - RESPPureToken("CASESENSITIVE", casesensitive).encode(into: &commandEncoder) + "TEXT".encode(into: &commandEncoder) + RESPPureToken("NOSTEM", nostem).encode(into: &commandEncoder) + suffixTrie.encode(into: &commandEncoder) + RESPWithToken("WEIGHT", weight).encode(into: &commandEncoder) } } - public enum SchemaFieldsFieldTypeVectorAlgorithm: RESPRenderable, Sendable, Hashable { + public enum SchemaFieldTypeVectorAlgorithm: RESPRenderable, Sendable, Hashable { case hnsw case flat @@ -673,43 +440,20 @@ public enum FT { } } } - public struct SchemaFieldsFieldTypeVectorVectorParams_Type: RESPRenderable, Sendable, Hashable { - - @inlinable - public init() { - } - - @inlinable - public var respEntries: Int { - "TYPE".respEntries + "FLOAT32".respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "TYPE".encode(into: &commandEncoder) - "FLOAT32".encode(into: &commandEncoder) - } - } - public struct SchemaFieldsFieldTypeVectorVectorParamsDim: RESPRenderable, Sendable, Hashable { - public var value: Int - - @inlinable - public init(value: Int) { - self.value = value - } + public enum SchemaFieldTypeVectorVectorParams_Type: RESPRenderable, Sendable, Hashable { + case float32 @inlinable - public var respEntries: Int { - "DIM".respEntries + value.respEntries - } + public var respEntries: Int { 1 } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "DIM".encode(into: &commandEncoder) - value.encode(into: &commandEncoder) + switch self { + case .float32: "FLOAT32".encode(into: &commandEncoder) + } } } - public enum SchemaFieldsFieldTypeVectorVectorParamsDistanceMetricMetric: RESPRenderable, Sendable, Hashable { + public enum SchemaFieldTypeVectorVectorParamsDistanceMetric: RESPRenderable, Sendable, Hashable { case l2 case ip case cosine @@ -726,129 +470,60 @@ public enum FT { } } } - public struct SchemaFieldsFieldTypeVectorVectorParamsDistanceMetric: RESPRenderable, Sendable, Hashable { - public var metric: SchemaFieldsFieldTypeVectorVectorParamsDistanceMetricMetric - - @inlinable - public init(metric: SchemaFieldsFieldTypeVectorVectorParamsDistanceMetricMetric) { - self.metric = metric - } - - @inlinable - public var respEntries: Int { - "DISTANCE_METRIC".respEntries + metric.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "DISTANCE_METRIC".encode(into: &commandEncoder) - metric.encode(into: &commandEncoder) - } - } - public struct SchemaFieldsFieldTypeVectorVectorParamsM: RESPRenderable, Sendable, Hashable { - public var value: Int - - @inlinable - public init(value: Int) { - self.value = value - } - - @inlinable - public var respEntries: Int { - "M".respEntries + value.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "M".encode(into: &commandEncoder) - value.encode(into: &commandEncoder) - } - } - public struct SchemaFieldsFieldTypeVectorVectorParamsEfConstruction: RESPRenderable, Sendable, Hashable { - public var value: Int - - @inlinable - public init(value: Int) { - self.value = value - } - - @inlinable - public var respEntries: Int { - "EF_CONSTRUCTION".respEntries + value.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "EF_CONSTRUCTION".encode(into: &commandEncoder) - value.encode(into: &commandEncoder) - } - } - public struct SchemaFieldsFieldTypeVectorVectorParamsBlockSize: RESPRenderable, Sendable, Hashable { - public var value: Int - - @inlinable - public init(value: Int) { - self.value = value - } - - @inlinable - public var respEntries: Int { - "BLOCK_SIZE".respEntries + value.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "BLOCK_SIZE".encode(into: &commandEncoder) - value.encode(into: &commandEncoder) - } - } - public struct SchemaFieldsFieldTypeVectorVectorParams: RESPRenderable, Sendable, Hashable { - public var type: SchemaFieldsFieldTypeVectorVectorParams_Type - public var dim: SchemaFieldsFieldTypeVectorVectorParamsDim - public var distanceMetric: SchemaFieldsFieldTypeVectorVectorParamsDistanceMetric - public var m: SchemaFieldsFieldTypeVectorVectorParamsM? - public var efConstruction: SchemaFieldsFieldTypeVectorVectorParamsEfConstruction? - public var blockSize: SchemaFieldsFieldTypeVectorVectorParamsBlockSize? + public struct SchemaFieldTypeVectorVectorParams: RESPRenderable, Sendable, Hashable { + public var type: SchemaFieldTypeVectorVectorParams_Type + public var dim: Int + public var distanceMetric: SchemaFieldTypeVectorVectorParamsDistanceMetric + public var initialCap: Int? + public var m: Int? + public var efConstruction: Int? + public var efRuntime: Int? @inlinable public init( - type: SchemaFieldsFieldTypeVectorVectorParams_Type, - dim: SchemaFieldsFieldTypeVectorVectorParamsDim, - distanceMetric: SchemaFieldsFieldTypeVectorVectorParamsDistanceMetric, - m: SchemaFieldsFieldTypeVectorVectorParamsM? = nil, - efConstruction: SchemaFieldsFieldTypeVectorVectorParamsEfConstruction? = nil, - blockSize: SchemaFieldsFieldTypeVectorVectorParamsBlockSize? = nil + type: SchemaFieldTypeVectorVectorParams_Type, + dim: Int, + distanceMetric: SchemaFieldTypeVectorVectorParamsDistanceMetric, + initialCap: Int? = nil, + m: Int? = nil, + efConstruction: Int? = nil, + efRuntime: Int? = nil ) { self.type = type self.dim = dim self.distanceMetric = distanceMetric + self.initialCap = initialCap self.m = m self.efConstruction = efConstruction - self.blockSize = blockSize + self.efRuntime = efRuntime } @inlinable public var respEntries: Int { - type.respEntries + dim.respEntries + distanceMetric.respEntries + m.respEntries + efConstruction.respEntries + blockSize.respEntries + RESPWithToken("TYPE", type).respEntries + RESPWithToken("DIM", dim).respEntries + + RESPWithToken("DISTANCE_METRIC", distanceMetric).respEntries + RESPWithToken("INITIAL_CAP", initialCap).respEntries + + RESPWithToken("M", m).respEntries + RESPWithToken("EF_CONSTRUCTION", efConstruction).respEntries + + RESPWithToken("EF_RUNTIME", efRuntime).respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - type.encode(into: &commandEncoder) - dim.encode(into: &commandEncoder) - distanceMetric.encode(into: &commandEncoder) - m.encode(into: &commandEncoder) - efConstruction.encode(into: &commandEncoder) - blockSize.encode(into: &commandEncoder) + RESPWithToken("TYPE", type).encode(into: &commandEncoder) + RESPWithToken("DIM", dim).encode(into: &commandEncoder) + RESPWithToken("DISTANCE_METRIC", distanceMetric).encode(into: &commandEncoder) + RESPWithToken("INITIAL_CAP", initialCap).encode(into: &commandEncoder) + RESPWithToken("M", m).encode(into: &commandEncoder) + RESPWithToken("EF_CONSTRUCTION", efConstruction).encode(into: &commandEncoder) + RESPWithToken("EF_RUNTIME", efRuntime).encode(into: &commandEncoder) } } - public struct SchemaFieldsFieldTypeVector: RESPRenderable, Sendable, Hashable { - public var algorithm: SchemaFieldsFieldTypeVectorAlgorithm + public struct SchemaFieldTypeVector: RESPRenderable, Sendable, Hashable { + public var algorithm: SchemaFieldTypeVectorAlgorithm public var attrCount: Int - public var vectorParams: SchemaFieldsFieldTypeVectorVectorParams + public var vectorParams: SchemaFieldTypeVectorVectorParams @inlinable - public init(algorithm: SchemaFieldsFieldTypeVectorAlgorithm, attrCount: Int, vectorParams: SchemaFieldsFieldTypeVectorVectorParams) { + public init(algorithm: SchemaFieldTypeVectorAlgorithm, attrCount: Int, vectorParams: SchemaFieldTypeVectorVectorParams) { self.algorithm = algorithm self.attrCount = attrCount self.vectorParams = vectorParams @@ -867,18 +542,18 @@ public enum FT { vectorParams.encode(into: &commandEncoder) } } - public enum SchemaFieldsFieldType: RESPRenderable, Sendable, Hashable { + public enum SchemaFieldType: RESPRenderable, Sendable, Hashable { case numeric - case text - case tag(SchemaFieldsFieldTypeTag) - case vector(SchemaFieldsFieldTypeVector) + case tag(SchemaFieldTypeTag) + case text(SchemaFieldTypeText) + case vector(SchemaFieldTypeVector) @inlinable public var respEntries: Int { switch self { case .numeric: "NUMERIC".respEntries - case .text: "TEXT".respEntries case .tag(let tag): tag.respEntries + case .text(let text): text.respEntries case .vector(let vector): vector.respEntries } } @@ -887,73 +562,97 @@ public enum FT { public func encode(into commandEncoder: inout ValkeyCommandEncoder) { switch self { case .numeric: "NUMERIC".encode(into: &commandEncoder) - case .text: "TEXT".encode(into: &commandEncoder) case .tag(let tag): tag.encode(into: &commandEncoder) + case .text(let text): text.encode(into: &commandEncoder) case .vector(let vector): vector.encode(into: &commandEncoder) } } } - public struct SchemaFields: RESPRenderable, Sendable, Hashable { + public struct Schema: RESPRenderable, Sendable, Hashable { public var fieldIdentifier: FieldIdentifier - public var alias: SchemaFieldsAlias? - public var fieldType: SchemaFieldsFieldType + public var alias: String? + public var fieldType: SchemaFieldType + public var sortable: Bool @inlinable - public init(fieldIdentifier: FieldIdentifier, alias: SchemaFieldsAlias? = nil, fieldType: SchemaFieldsFieldType) { + public init(fieldIdentifier: FieldIdentifier, alias: String? = nil, fieldType: SchemaFieldType, sortable: Bool = false) { self.fieldIdentifier = fieldIdentifier self.alias = alias self.fieldType = fieldType + self.sortable = sortable } @inlinable public var respEntries: Int { - RESPRenderableBulkString(fieldIdentifier).respEntries + alias.respEntries + fieldType.respEntries + RESPRenderableBulkString(fieldIdentifier).respEntries + RESPWithToken("AS", alias).respEntries + fieldType.respEntries + + RESPPureToken("SORTABLE", sortable).respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { RESPRenderableBulkString(fieldIdentifier).encode(into: &commandEncoder) - alias.encode(into: &commandEncoder) + RESPWithToken("AS", alias).encode(into: &commandEncoder) fieldType.encode(into: &commandEncoder) - } - } - public struct Schema: RESPRenderable, Sendable, Hashable { - public var fields: [SchemaFields] - - @inlinable - public init(fields: [SchemaFields]) { - self.fields = fields - } - - @inlinable - public var respEntries: Int { - "SCHEMA".respEntries + fields.respEntries - } - - @inlinable - public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "SCHEMA".encode(into: &commandEncoder) - fields.encode(into: &commandEncoder) + RESPPureToken("SORTABLE", sortable).encode(into: &commandEncoder) } } @inlinable public static var name: String { "FT.CREATE" } public var indexName: IndexName public var on: On? - public var prefix: Prefix? - public var schema: Schema + public var prefixes: [String] + public var score: String? + public var language: String? + public var skipinitialscan: Bool + public var minstemsize: Int? + public var offsets: Offsets? + public var stopwords: Stopwords? + public var punctuation: String? + public var schemas: [Schema] - @inlinable public init(indexName: IndexName, on: On? = nil, prefix: Prefix? = nil, schema: Schema) { + @inlinable public init( + indexName: IndexName, + on: On? = nil, + prefixes: [String] = [], + score: String? = nil, + language: String? = nil, + skipinitialscan: Bool = false, + minstemsize: Int? = nil, + offsets: Offsets? = nil, + stopwords: Stopwords? = nil, + punctuation: String? = nil, + schemas: [Schema] + ) { self.indexName = indexName self.on = on - self.prefix = prefix - self.schema = schema + self.prefixes = prefixes + self.score = score + self.language = language + self.skipinitialscan = skipinitialscan + self.minstemsize = minstemsize + self.offsets = offsets + self.stopwords = stopwords + self.punctuation = punctuation + self.schemas = schemas } public var keysAffected: [ValkeyKey] { [] } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - commandEncoder.encodeArray("FT.CREATE", RESPRenderableBulkString(indexName), on, prefix, schema) + commandEncoder.encodeArray( + "FT.CREATE", + RESPRenderableBulkString(indexName), + RESPWithToken("ON", on), + RESPArrayWithTokenAndCount("PREFIX", prefixes), + RESPWithToken("SCORE", score), + RESPWithToken("LANGUAGE", language), + RESPPureToken("SKIPINITIALSCAN", skipinitialscan), + RESPWithToken("MINSTEMSIZE", minstemsize), + offsets, + stopwords, + RESPWithToken("PUNCTUATION", punctuation), + RESPWithToken("SCHEMA", schemas) + ) } } @@ -962,16 +661,16 @@ public enum FT { public struct DROPINDEX: ValkeyCommand { @inlinable public static var name: String { "FT.DROPINDEX" } - public var key: ValkeyKey + public var indexName: ValkeyKey - @inlinable public init(_ key: ValkeyKey) { - self.key = key + @inlinable public init(indexName: ValkeyKey) { + self.indexName = indexName } - public var keysAffected: CollectionOfOne { .init(key) } + public var keysAffected: CollectionOfOne { .init(indexName) } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - commandEncoder.encodeArray("FT.DROPINDEX", key) + commandEncoder.encodeArray("FT.DROPINDEX", indexName) } } @@ -980,7 +679,8 @@ public enum FT { public struct INFO: ValkeyCommand { public enum Scope: RESPRenderable, Sendable, Hashable { case local - case global + case primary + case cluster @inlinable public var respEntries: Int { 1 } @@ -989,166 +689,246 @@ public enum FT { public func encode(into commandEncoder: inout ValkeyCommandEncoder) { switch self { case .local: "LOCAL".encode(into: &commandEncoder) - case .global: "GLOBAL".encode(into: &commandEncoder) + case .primary: "PRIMARY".encode(into: &commandEncoder) + case .cluster: "CLUSTER".encode(into: &commandEncoder) + } + } + } + public enum ShardPolicy: RESPRenderable, Sendable, Hashable { + case allshards + case someshards + + @inlinable + public var respEntries: Int { 1 } + + @inlinable + public func encode(into commandEncoder: inout ValkeyCommandEncoder) { + switch self { + case .allshards: "ALLSHARDS".encode(into: &commandEncoder) + case .someshards: "SOMESHARDS".encode(into: &commandEncoder) + } + } + } + public enum Consistency: RESPRenderable, Sendable, Hashable { + case consistent + case inconsistent + + @inlinable + public var respEntries: Int { 1 } + + @inlinable + public func encode(into commandEncoder: inout ValkeyCommandEncoder) { + switch self { + case .consistent: "CONSISTENT".encode(into: &commandEncoder) + case .inconsistent: "INCONSISTENT".encode(into: &commandEncoder) } } } @inlinable public static var name: String { "FT.INFO" } - public var key: ValkeyKey + public var indexName: ValkeyKey public var scope: Scope? + public var shardPolicy: ShardPolicy? + public var consistency: Consistency? - @inlinable public init(_ key: ValkeyKey, scope: Scope? = nil) { - self.key = key + @inlinable public init(indexName: ValkeyKey, scope: Scope? = nil, shardPolicy: ShardPolicy? = nil, consistency: Consistency? = nil) { + self.indexName = indexName self.scope = scope + self.shardPolicy = shardPolicy + self.consistency = consistency } - public var keysAffected: CollectionOfOne { .init(key) } + public var keysAffected: CollectionOfOne { .init(indexName) } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - commandEncoder.encodeArray("FT.INFO", key, scope) + commandEncoder.encodeArray("FT.INFO", indexName, scope, shardPolicy, consistency) } } /// Performs a search of the specified index. The keys which match the query expression are returned @_documentation(visibility: internal) public struct SEARCH: ValkeyCommand { - public struct Timeout: RESPRenderable, Sendable, Hashable { - public var timeoutMs: Int + public enum ShardPolicy: RESPRenderable, Sendable, Hashable { + case allshards + case someshards @inlinable - public init(timeoutMs: Int) { - self.timeoutMs = timeoutMs - } + public var respEntries: Int { 1 } @inlinable - public var respEntries: Int { - "TIMEOUT".respEntries + timeoutMs.respEntries + public func encode(into commandEncoder: inout ValkeyCommandEncoder) { + switch self { + case .allshards: "ALLSHARDS".encode(into: &commandEncoder) + case .someshards: "SOMESHARDS".encode(into: &commandEncoder) + } } + } + public enum Consistency: RESPRenderable, Sendable, Hashable { + case consistent + case inconsistent + + @inlinable + public var respEntries: Int { 1 } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "TIMEOUT".encode(into: &commandEncoder) - timeoutMs.encode(into: &commandEncoder) + switch self { + case .consistent: "CONSISTENT".encode(into: &commandEncoder) + case .inconsistent: "INCONSISTENT".encode(into: &commandEncoder) + } } } - public struct Params: RESPRenderable, Sendable, Hashable { + public struct Limit: RESPRenderable, Sendable, Hashable { + public var offset: Int public var count: Int - public var pairs: [String] @inlinable - public init(count: Int, pairs: [String]) { + public init(offset: Int, count: Int) { + self.offset = offset self.count = count - self.pairs = pairs } @inlinable public var respEntries: Int { - "PARAMS".respEntries + count.respEntries + pairs.respEntries + "LIMIT".respEntries + offset.respEntries + count.respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "PARAMS".encode(into: &commandEncoder) + "LIMIT".encode(into: &commandEncoder) + offset.encode(into: &commandEncoder) count.encode(into: &commandEncoder) - pairs.encode(into: &commandEncoder) } } - public struct ReturnFields: RESPRenderable, Sendable, Hashable { - public var count: Int - public var fields: [String] + public struct Params: RESPRenderable, Sendable, Hashable { + public var name: String + public var value: String @inlinable - public init(count: Int, fields: [String]) { - self.count = count - self.fields = fields + public init(name: String, value: String) { + self.name = name + self.value = value } @inlinable public var respEntries: Int { - "RETURN".respEntries + count.respEntries + fields.respEntries + name.respEntries + value.respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "RETURN".encode(into: &commandEncoder) - count.encode(into: &commandEncoder) - fields.encode(into: &commandEncoder) + name.encode(into: &commandEncoder) + value.encode(into: &commandEncoder) } } - public struct Limit: RESPRenderable, Sendable, Hashable { - public var offset: Int - public var count: Int + public struct Return: RESPRenderable, Sendable, Hashable { + public var field: String + public var alias: String? @inlinable - public init(offset: Int, count: Int) { - self.offset = offset - self.count = count + public init(field: String, alias: String? = nil) { + self.field = field + self.alias = alias } @inlinable public var respEntries: Int { - "LIMIT".respEntries + offset.respEntries + count.respEntries + field.respEntries + RESPWithToken("AS", alias).respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "LIMIT".encode(into: &commandEncoder) - offset.encode(into: &commandEncoder) - count.encode(into: &commandEncoder) + field.encode(into: &commandEncoder) + RESPWithToken("AS", alias).encode(into: &commandEncoder) + } + } + public enum SortbyDirection: RESPRenderable, Sendable, Hashable { + case asc + case desc + + @inlinable + public var respEntries: Int { 1 } + + @inlinable + public func encode(into commandEncoder: inout ValkeyCommandEncoder) { + switch self { + case .asc: "ASC".encode(into: &commandEncoder) + case .desc: "DESC".encode(into: &commandEncoder) + } } } - public struct Dialect: RESPRenderable, Sendable, Hashable { - public var dialect: Int + public struct Sortby: RESPRenderable, Sendable, Hashable { + public var field: String + public var direction: SortbyDirection? @inlinable - public init(dialect: Int) { - self.dialect = dialect + public init(field: String, direction: SortbyDirection? = nil) { + self.field = field + self.direction = direction } @inlinable public var respEntries: Int { - "DIALECT".respEntries + dialect.respEntries + "SORTBY".respEntries + field.respEntries + direction.respEntries } @inlinable public func encode(into commandEncoder: inout ValkeyCommandEncoder) { - "DIALECT".encode(into: &commandEncoder) - dialect.encode(into: &commandEncoder) + "SORTBY".encode(into: &commandEncoder) + field.encode(into: &commandEncoder) + direction.encode(into: &commandEncoder) } } @inlinable public static var name: String { "FT.SEARCH" } public var index: ValkeyKey public var query: Query - public var nocontent: Bool - public var timeout: Timeout? - public var params: Params? - public var returnFields: ReturnFields? + public var shardPolicy: ShardPolicy? + public var consistency: Consistency? + public var dialect: Int? + public var inorder: Bool public var limit: Limit? - public var dialect: Dialect? - public var localonly: Bool + public var nocontent: Bool + public var params: [Params] + public var returns: [Return] + public var slop: Int? + public var sortby: Sortby? + public var timeout: Int? + public var verbatim: Bool + public var withsortkeys: Bool @inlinable public init( index: ValkeyKey, query: Query, - nocontent: Bool = false, - timeout: Timeout? = nil, - params: Params? = nil, - returnFields: ReturnFields? = nil, + shardPolicy: ShardPolicy? = nil, + consistency: Consistency? = nil, + dialect: Int? = nil, + inorder: Bool = false, limit: Limit? = nil, - dialect: Dialect? = nil, - localonly: Bool = false + nocontent: Bool = false, + params: [Params] = [], + returns: [Return] = [], + slop: Int? = nil, + sortby: Sortby? = nil, + timeout: Int? = nil, + verbatim: Bool = false, + withsortkeys: Bool = false ) { self.index = index self.query = query + self.shardPolicy = shardPolicy + self.consistency = consistency + self.dialect = dialect + self.inorder = inorder + self.limit = limit self.nocontent = nocontent - self.timeout = timeout self.params = params - self.returnFields = returnFields - self.limit = limit - self.dialect = dialect - self.localonly = localonly + self.returns = returns + self.slop = slop + self.sortby = sortby + self.timeout = timeout + self.verbatim = verbatim + self.withsortkeys = withsortkeys } public var keysAffected: CollectionOfOne { .init(index) } @@ -1158,13 +938,19 @@ public enum FT { "FT.SEARCH", index, RESPRenderableBulkString(query), - RESPPureToken("NOCONTENT", nocontent), - timeout, - params, - returnFields, + shardPolicy, + consistency, + RESPWithToken("DIALECT", dialect), + RESPPureToken("INORDER", inorder), limit, - dialect, - RESPPureToken("LOCALONLY", localonly) + RESPPureToken("NOCONTENT", nocontent), + RESPArrayWithTokenAndParameterCount("PARAMS", params), + RESPArrayWithTokenAndCount("RETURN", returns), + RESPWithToken("SLOP", slop), + sortby, + RESPWithToken("TIMEOUT", timeout), + RESPPureToken("VERBATIM", verbatim), + RESPPureToken("WITHSORTKEYS", withsortkeys) ) } } @@ -1206,45 +992,43 @@ extension ValkeyClientProtocol { /// Performs a search of the specified index. The keys which match the query expression are subjected to further processing as specified /// /// - Documentation: [FT.AGGREGATE](https://valkey.io/commands/ft.aggregate) + /// - History: + /// * 1.2.0: Added Text Indexing and Search support /// - Complexity: O(log N) @inlinable @discardableResult public func ftAggregate( index: ValkeyKey, query: Query, - verbatim: Bool = false, + dialect: Int? = nil, + inorder: Bool = false, load: FT.AGGREGATE.Load? = nil, - groupbys: [FT.AGGREGATE.Groupby] = [], - reduces: [FT.AGGREGATE.Reduce] = [], - sortby: FT.AGGREGATE.Sortby? = nil, + params: [FT.AGGREGATE.Params] = [], + slop: Int? = nil, + timeout: Int? = nil, + verbatim: Bool = false, applys: [FT.AGGREGATE.Apply] = [], - limit: FT.AGGREGATE.Limit? = nil, - filters: [FT.AGGREGATE.Filter] = [], - withcursor: FT.AGGREGATE.Withcursor? = nil, - timeout: FT.AGGREGATE.Timeout? = nil, - params: FT.AGGREGATE.Params? = nil, - scorer: FT.AGGREGATE.Scorer? = nil, - addscores: Bool = false, - dialect: FT.AGGREGATE.Dialect? = nil + filters: [String] = [], + groupbys: [FT.AGGREGATE.Groupby] = [], + limits: [FT.AGGREGATE.Limit] = [], + sortby: FT.AGGREGATE.Sortby? = nil ) async throws(ValkeyClientError) -> RESPToken { try await execute( FT.AGGREGATE( index: index, query: query, - verbatim: verbatim, + dialect: dialect, + inorder: inorder, load: load, - groupbys: groupbys, - reduces: reduces, - sortby: sortby, + params: params, + slop: slop, + timeout: timeout, + verbatim: verbatim, applys: applys, - limit: limit, filters: filters, - withcursor: withcursor, - timeout: timeout, - params: params, - scorer: scorer, - addscores: addscores, - dialect: dialect + groupbys: groupbys, + limits: limits, + sortby: sortby ) ) } @@ -1252,16 +1036,39 @@ extension ValkeyClientProtocol { /// Creates an empty search index and initiates the backfill process /// /// - Documentation: [FT.CREATE](https://valkey.io/commands/ft.create) + /// - History: + /// * 1.2.0: Added Text Indexing and Search support /// - Complexity: Construction time O(N log N), where N is the number of indexed items @inlinable @discardableResult public func ftCreate( indexName: IndexName, on: FT.CREATE.On? = nil, - prefix: FT.CREATE.Prefix? = nil, - schema: FT.CREATE.Schema + prefixes: [String] = [], + score: String? = nil, + language: String? = nil, + skipinitialscan: Bool = false, + minstemsize: Int? = nil, + offsets: FT.CREATE.Offsets? = nil, + stopwords: FT.CREATE.Stopwords? = nil, + punctuation: String? = nil, + schemas: [FT.CREATE.Schema] ) async throws(ValkeyClientError) -> RESPToken { - try await execute(FT.CREATE(indexName: indexName, on: on, prefix: prefix, schema: schema)) + try await execute( + FT.CREATE( + indexName: indexName, + on: on, + prefixes: prefixes, + score: score, + language: language, + skipinitialscan: skipinitialscan, + minstemsize: minstemsize, + offsets: offsets, + stopwords: stopwords, + punctuation: punctuation, + schemas: schemas + ) + ) } /// Drop the index created by FT.CREATE command. It is an error if the index doesn't exist @@ -1270,48 +1077,69 @@ extension ValkeyClientProtocol { /// - Complexity: O(N) @inlinable @discardableResult - public func ftDropindex(_ key: ValkeyKey) async throws(ValkeyClientError) -> FT.DROPINDEX.Response { - try await execute(FT.DROPINDEX(key)) + public func ftDropindex(indexName: ValkeyKey) async throws(ValkeyClientError) -> FT.DROPINDEX.Response { + try await execute(FT.DROPINDEX(indexName: indexName)) } /// Detailed information about the specified index is returned /// /// - Documentation: [FT.INFO](https://valkey.io/commands/ft.info) + /// - History: + /// * 1.2.0: Added Text Indexing and Search support /// - Complexity: O(1) @inlinable @discardableResult - public func ftInfo(_ key: ValkeyKey, scope: FT.INFO.Scope? = nil) async throws(ValkeyClientError) -> FT.INFO.Response { - try await execute(FT.INFO(key, scope: scope)) + public func ftInfo( + indexName: ValkeyKey, + scope: FT.INFO.Scope? = nil, + shardPolicy: FT.INFO.ShardPolicy? = nil, + consistency: FT.INFO.Consistency? = nil + ) async throws(ValkeyClientError) -> FT.INFO.Response { + try await execute(FT.INFO(indexName: indexName, scope: scope, shardPolicy: shardPolicy, consistency: consistency)) } /// Performs a search of the specified index. The keys which match the query expression are returned /// /// - Documentation: [FT.SEARCH](https://valkey.io/commands/ft.search) + /// - History: + /// * 1.2.0: Added Text Indexing and Search support /// - Complexity: O(log N) @inlinable @discardableResult public func ftSearch( index: ValkeyKey, query: Query, - nocontent: Bool = false, - timeout: FT.SEARCH.Timeout? = nil, - params: FT.SEARCH.Params? = nil, - returnFields: FT.SEARCH.ReturnFields? = nil, + shardPolicy: FT.SEARCH.ShardPolicy? = nil, + consistency: FT.SEARCH.Consistency? = nil, + dialect: Int? = nil, + inorder: Bool = false, limit: FT.SEARCH.Limit? = nil, - dialect: FT.SEARCH.Dialect? = nil, - localonly: Bool = false + nocontent: Bool = false, + params: [FT.SEARCH.Params] = [], + returns: [FT.SEARCH.Return] = [], + slop: Int? = nil, + sortby: FT.SEARCH.Sortby? = nil, + timeout: Int? = nil, + verbatim: Bool = false, + withsortkeys: Bool = false ) async throws(ValkeyClientError) -> RESPToken { try await execute( FT.SEARCH( index: index, query: query, + shardPolicy: shardPolicy, + consistency: consistency, + dialect: dialect, + inorder: inorder, + limit: limit, nocontent: nocontent, - timeout: timeout, params: params, - returnFields: returnFields, - limit: limit, - dialect: dialect, - localonly: localonly + returns: returns, + slop: slop, + sortby: sortby, + timeout: timeout, + verbatim: verbatim, + withsortkeys: withsortkeys ) ) } diff --git a/Sources/_ValkeyCommandsBuilder/Resources/valkey-search-commands.json b/Sources/_ValkeyCommandsBuilder/Resources/valkey-search-commands.json index 762d4ad3..14697837 100755 --- a/Sources/_ValkeyCommandsBuilder/Resources/valkey-search-commands.json +++ b/Sources/_ValkeyCommandsBuilder/Resources/valkey-search-commands.json @@ -32,6 +32,12 @@ "SLOW", "SEARCH" ], + "history": [ + [ + "1.2.0", + "Added Text Indexing and Search support" + ] + ], "arguments": [ { "key_spec_index": 0, @@ -44,10 +50,26 @@ "type": "string" }, { - "name": "verbatim", + "name": "DIALECT", + "type": "block", + "optional": true, + "arguments": [ + { + "name": "dialect_token", + "type": "pure-token", + "token": "DIALECT" + }, + { + "name": "dialect_version", + "type": "integer" + } + ] + }, + { + "name": "INORDER", "type": "pure-token", "optional": true, - "token": "VERBATIM" + "token": "INORDER" }, { "name": "LOAD", @@ -60,31 +82,26 @@ "token": "LOAD" }, { - "name": "nargs", - "type": "integer" - }, - { - "name": "items", - "type": "block", - "multiple": true, + "name": "load_args", + "type": "oneof", "arguments": [ { - "name": "identifier", - "type": "string" + "name": "all", + "type": "pure-token", + "token": "*" }, { - "name": "alias", + "name": "fields", "type": "block", - "optional": true, "arguments": [ { - "name": "as_token", - "type": "pure-token", - "token": "AS" + "name": "count", + "type": "integer" }, { - "name": "property", - "type": "string" + "name": "field", + "type": "string", + "multiple": true } ] } @@ -93,174 +110,74 @@ ] }, { - "name": "GROUPBY", + "name": "PARAMS", "type": "block", "optional": true, - "multiple": true, "arguments": [ { - "name": "groupby_token", + "name": "params_token", "type": "pure-token", - "token": "GROUPBY" + "token": "PARAMS" }, { - "name": "nargs", + "name": "count", "type": "integer" }, { - "name": "group_fields", - "type": "string", - "multiple": true + "name": "parameters", + "type": "block", + "multiple": true, + "arguments": [ + { + "name": "name", + "type": "string" + }, + { + "name": "value", + "type": "string" + } + ] } ] }, { - "name": "REDUCE", + "name": "SLOP", "type": "block", "optional": true, - "multiple": true, "arguments": [ { - "name": "reduce_token", + "name": "slop_token", "type": "pure-token", - "token": "REDUCE" + "token": "SLOP" }, { - "name": "function", - "type": "oneof", - "arguments": [ - { - "name": "count", - "type": "pure-token", - "token": "COUNT" - }, - { - "name": "count_distinct", - "type": "pure-token", - "token": "COUNT_DISTINCT" - }, - { - "name": "count_distinctish", - "type": "pure-token", - "token": "COUNT_DISTINCT_ISH" - }, - { - "name": "sum", - "type": "pure-token", - "token": "SUM" - }, - { - "name": "min", - "type": "pure-token", - "token": "MIN" - }, - { - "name": "max", - "type": "pure-token", - "token": "MAX" - }, - { - "name": "avg", - "type": "pure-token", - "token": "AVG" - }, - { - "name": "stddev", - "type": "pure-token", - "token": "STDDEV" - }, - { - "name": "quantile", - "type": "pure-token", - "token": "QUANTILE" - }, - { - "name": "tolist", - "type": "pure-token", - "token": "TOLIST" - }, - { - "name": "first_value", - "type": "pure-token", - "token": "FIRST_VALUE" - }, - { - "name": "random_sample", - "type": "pure-token", - "token": "RANDOM_SAMPLE" - } - ] - }, - { - "name": "nargs", + "name": "slop", "type": "integer" - }, - { - "name": "identifier", - "type": "string", - "multiple": true - }, - { - "name": "alias", - "type": "block", - "optional": true, - "arguments": [ - { - "name": "as_token", - "type": "pure-token", - "token": "AS" - }, - { - "name": "identifier", - "type": "string" - } - ] } ] }, { - "name": "SORTBY", + "name": "TIMEOUT", "type": "block", "optional": true, "arguments": [ { - "name": "sortby_token", + "name": "timeout_token", "type": "pure-token", - "token": "SORTBY" + "token": "TIMEOUT" }, { - "name": "nargs", + "name": "milliseconds", "type": "integer" - }, - { - "name": "sort_params", - "type": "string", - "multiple": true - }, - { - "name": "max", - "type": "block", - "optional": true, - "arguments": [ - { - "name": "max_token", - "type": "pure-token", - "token": "MAX" - }, - { - "name": "num", - "type": "integer" - } - ] - }, - { - "name": "WITHCOUNT", - "type": "pure-token", - "optional": true, - "token": "WITHCOUNT" } ] }, + { + "name": "VERBATIM", + "type": "pure-token", + "optional": true, + "token": "VERBATIM" + }, { "name": "APPLY", "type": "block", @@ -273,7 +190,7 @@ "token": "APPLY" }, { - "name": "expr", + "name": "expression", "type": "string" }, { @@ -282,243 +199,379 @@ "token": "AS" }, { - "name": "name", + "name": "field", "type": "string" } ] }, { - "name": "LIMIT", + "name": "FILTER", "type": "block", "optional": true, + "multiple": true, "arguments": [ { - "name": "limit_token", + "name": "filter_token", "type": "pure-token", - "token": "LIMIT" + "token": "FILTER" }, { - "name": "offset", - "type": "integer" + "name": "expression", + "type": "string" + } + ] + }, + { + "name": "GROUPBY", + "type": "block", + "optional": true, + "multiple": true, + "arguments": [ + { + "name": "groupby_token", + "type": "pure-token", + "token": "GROUPBY" }, { - "name": "num", + "name": "count", "type": "integer" + }, + { + "name": "field", + "type": "string", + "multiple": true + }, + { + "name": "REDUCE", + "type": "block", + "optional": true, + "multiple": true, + "arguments": [ + { + "name": "reduce_token", + "type": "pure-token", + "token": "REDUCE" + }, + { + "name": "function", + "type": "oneof", + "arguments": [ + { + "name": "COUNT", + "type": "pure-token", + "token": "COUNT" + }, + { + "name": "COUNT_DISTINCT", + "type": "pure-token", + "token": "COUNT_DISTINCT" + }, + { + "name": "SUM", + "type": "pure-token", + "token": "SUM" + }, + { + "name": "MIN", + "type": "pure-token", + "token": "MIN" + }, + { + "name": "MAX", + "type": "pure-token", + "token": "MAX" + }, + { + "name": "AVG", + "type": "pure-token", + "token": "AVG" + }, + { + "name": "STDDEV", + "type": "pure-token", + "token": "STDDEV" + } + ] + }, + { + "name": "count", + "type": "integer" + }, + { + "name": "expression", + "type": "string", + "multiple": true, + "optional": true + }, + { + "name": "alias", + "type": "block", + "optional": true, + "arguments": [ + { + "name": "as_token", + "type": "pure-token", + "token": "AS" + }, + { + "name": "name", + "type": "string" + } + ] + } + ] } ] }, { - "name": "FILTER", + "name": "LIMIT", "type": "block", "optional": true, "multiple": true, "arguments": [ { - "name": "filter_token", + "name": "limit_token", "type": "pure-token", - "token": "FILTER" + "token": "LIMIT" }, { - "name": "expr", - "type": "string" + "name": "offset", + "type": "integer" + }, + { + "name": "count", + "type": "integer" } ] }, { - "name": "WITHCURSOR", + "name": "SORTBY", "type": "block", "optional": true, "arguments": [ { - "name": "withcursor_token", + "name": "sortby_token", "type": "pure-token", - "token": "WITHCURSOR" + "token": "SORTBY" }, { "name": "count", + "type": "integer" + }, + { + "name": "sort_params", + "type": "string", + "multiple": true + }, + { + "name": "max", "type": "block", + "optional": true, "arguments": [ { - "name": "count_token", + "name": "max_token", "type": "pure-token", - "token": "COUNT" + "token": "MAX" }, { - "name": "read_size", + "name": "num", "type": "integer" } ] + } + ] + } + ], + "arity": -3, + "complexity": "O(log N)", + "group": "search", + "module_since": "1.1.0", + "summary": "Performs a search of the specified index. The keys which match the query expression are subjected to further processing as specified" + }, + "FT.CREATE": { + "acl_categories": [ + "FAST", + "WRITE", + "SEARCH" + ], + "history": [ + [ + "1.2.0", + "Added Text Indexing and Search support" + ] + ], + "arguments": [ + { + "key_spec_index": 0, + "name": "index-name", + "type": "string" + }, + { + "name": "on", + "type": "block", + "optional": true, + "description": "Index source type", + "arguments": [ + { + "name": "on_token", + "type": "pure-token", + "token": "ON" }, { - "name": "maxidle", - "type": "block", - "optional": true, + "name": "type", + "type": "oneof", "arguments": [ { - "name": "maxidle_token", + "name": "hash", "type": "pure-token", - "token": "MAXIDLE" + "token": "HASH" }, { - "name": "idle_time", - "type": "integer" + "name": "json", + "type": "pure-token", + "token": "JSON" } ] } ] }, { - "name": "TIMEOUT", + "name": "prefix", "type": "block", "optional": true, "arguments": [ { - "name": "timeout_token", + "name": "prefix_token", "type": "pure-token", - "token": "TIMEOUT" + "token": "PREFIX" }, { - "name": "milliseconds", + "name": "count", "type": "integer" + }, + { + "name": "prefix", + "type": "string", + "multiple": true } ] }, { - "name": "PARAMS", + "name": "SCORE", "type": "block", "optional": true, "arguments": [ { - "name": "params_token", + "name": "score_token", "type": "pure-token", - "token": "PARAMS" + "token": "SCORE" }, { - "name": "nargs", - "type": "integer" - }, - { - "name": "parameters", - "type": "block", - "multiple": true, - "arguments": [ - { - "name": "name", - "type": "string" - }, - { - "name": "value", - "type": "string" - } - ] + "name": "default_value", + "type": "string" } ] }, { - "name": "SCORER", + "name": "LANGUAGE", "type": "block", "optional": true, "arguments": [ { - "name": "scorer_token", + "name": "language_token", "type": "pure-token", - "token": "SCORER" + "token": "LANGUAGE" }, { - "name": "scorer", + "name": "language", "type": "string" } ] }, { - "name": "ADDSCORES", + "name": "SKIPINITIALSCAN", "type": "pure-token", "optional": true, - "token": "ADDSCORES" + "token": "SKIPINITIALSCAN" }, { - "name": "DIALECT", + "name": "MINSTEMSIZE", "type": "block", "optional": true, "arguments": [ { - "name": "dialect_token", + "name": "minstemsize_token", "type": "pure-token", - "token": "DIALECT" + "token": "MINSTEMSIZE" }, { - "name": "dialect_version", + "name": "min_stem_size", "type": "integer" } ] - } - ], - "arity": -3, - "complexity": "O(log N)", - "group": "search", - "module_since": "1.1.0", - "summary": "Performs a search of the specified index. The keys which match the query expression are subjected to further processing as specified" - }, - "FT.CREATE": { - "acl_categories": [ - "FAST", - "WRITE", - "SEARCH" - ], - "arguments": [ + }, { - "key_spec_index": 0, - "name": "index-name", - "type": "string" + "name": "offsets", + "type": "oneof", + "optional": true, + "arguments": [ + { + "name": "WITHOFFSETS", + "type": "pure-token", + "token": "WITHOFFSETS" + }, + { + "name": "NOOFFSETS", + "type": "pure-token", + "token": "NOOFFSETS" + } + ] }, { - "name": "on", - "type": "block", + "name": "stopwords", + "type": "oneof", "optional": true, - "description": "Index source type", "arguments": [ { - "name": "on_token", + "name": "NOSTOPWORDS", "type": "pure-token", - "token": "ON" + "token": "NOSTOPWORDS" }, { - "name": "type", - "type": "oneof", + "name": "stopwords_list", + "type": "block", "arguments": [ { - "name": "hash", + "name": "stopwords_token", "type": "pure-token", - "token": "HASH" + "token": "STOPWORDS" }, { - "name": "json", - "type": "pure-token", - "token": "JSON" + "name": "count", + "type": "integer" + }, + { + "name": "word", + "type": "string", + "multiple": true } ] } ] }, { - "name": "prefix", + "name": "PUNCTUATION", "type": "block", "optional": true, - "description": "Optional PREFIX clause to restrict indexed keys", "arguments": [ { - "name": "PREFIX", + "name": "punctuation_token", "type": "pure-token", - "token": "PREFIX" - }, - { - "name": "count", - "type": "integer" + "token": "PUNCTUATION" }, { - "name": "prefix", - "type": "string", - "multiple": true + "name": "punctuation", + "type": "string" } ] }, @@ -547,12 +600,12 @@ "type": "block", "arguments": [ { - "name": "AS", + "name": "as_token", "type": "pure-token", "token": "AS" }, { - "name": "field-identifier", + "name": "field-alias", "type": "string" } ] @@ -566,11 +619,6 @@ "type": "pure-token", "token": "NUMERIC" }, - { - "name": "TEXT", - "type": "pure-token", - "token": "TEXT" - }, { "name": "TAG", "type": "block", @@ -597,7 +645,7 @@ ] }, { - "name": "casesensitive", + "name": "CASESENSITIVE", "optional": true, "type": "pure-token", "token": "CASESENSITIVE" @@ -605,11 +653,61 @@ ] }, { - "name": "vector", + "name": "TEXT", + "type": "block", + "arguments": [ + { + "name": "text_token", + "type": "pure-token", + "token": "TEXT" + }, + { + "name": "NOSTEM", + "optional": true, + "type": "pure-token", + "token": "NOSTEM" + }, + { + "name": "suffix_trie", + "type": "oneof", + "optional": true, + "arguments": [ + { + "name": "WITHSUFFIXTRIE", + "type": "pure-token", + "token": "WITHSUFFIXTRIE" + }, + { + "name": "NOSUFFIXTRIE", + "type": "pure-token", + "token": "NOSUFFIXTRIE" + } + ] + }, + { + "name": "weight", + "optional": true, + "type": "block", + "arguments": [ + { + "name": "weight_token", + "type": "pure-token", + "token": "WEIGHT" + }, + { + "name": "weight", + "type": "string" + } + ] + } + ] + }, + { + "name": "VECTOR", "type": "block", "arguments": [ { - "name": "vector", + "name": "vector_token", "type": "pure-token", "token": "VECTOR" }, @@ -636,7 +734,7 @@ { "name": "vector-params", "type": "block", - "description": "Vector algorithm parameters (DIM, TYPE, DISTANCE_METRIC, INITIAL_CAP, M, EF_CONSTRUCTION, EF_RUNTIME, BLOCK_SIZE)", + "description": "Vector algorithm parameters (DIM, TYPE, DISTANCE_METRIC, INITIAL_CAP, M, EF_CONSTRUCTION, EF_RUNTIME)", "arguments": [ { "name": "type", @@ -683,17 +781,17 @@ "type": "oneof", "arguments": [ { - "name": "l2", + "name": "L2", "type": "pure-token", "token": "L2" }, { - "name": "ip", + "name": "IP", "type": "pure-token", "token": "IP" }, { - "name": "cosine", + "name": "COSINE", "type": "pure-token", "token": "COSINE" } @@ -701,6 +799,22 @@ } ] }, + { + "name": "initial_cap", + "type": "block", + "optional": true, + "arguments": [ + { + "name": "ic_token", + "type": "pure-token", + "token": "INITIAL_CAP" + }, + { + "name": "value", + "type": "integer" + } + ] + }, { "name": "m", "type": "block", @@ -734,14 +848,14 @@ ] }, { - "name": "block_size", + "name": "ef_runtime", "type": "block", "optional": true, "arguments": [ { - "name": "bs_token", + "name": "efr_token", "type": "pure-token", - "token": "BLOCK_SIZE" + "token": "EF_RUNTIME" }, { "name": "value", @@ -754,6 +868,12 @@ ] } ] + }, + { + "name": "SORTABLE", + "optional": true, + "type": "pure-token", + "token": "SORTABLE" } ] } @@ -774,7 +894,7 @@ "arguments": [ { "key_spec_index": 0, - "name": "key", + "name": "index-name", "type": "key" } ], @@ -790,10 +910,16 @@ "FAST", "SEARCH" ], + "history": [ + [ + "1.2.0", + "Added Text Indexing and Search support" + ] + ], "arguments": [ { "key_spec_index": 0, - "name": "key", + "name": "index-name", "type": "key" }, { @@ -807,9 +933,48 @@ "token": "LOCAL" }, { - "name": "GLOBAL", + "name": "PRIMARY", + "type": "pure-token", + "token": "PRIMARY" + }, + { + "name": "CLUSTER", + "type": "pure-token", + "token": "CLUSTER" + } + ] + }, + { + "name": "shard-policy", + "type": "oneof", + "optional": true, + "arguments": [ + { + "name": "ALLSHARDS", + "type": "pure-token", + "token": "ALLSHARDS" + }, + { + "name": "SOMESHARDS", + "type": "pure-token", + "token": "SOMESHARDS" + } + ] + }, + { + "name": "consistency", + "type": "oneof", + "optional": true, + "arguments": [ + { + "name": "CONSISTENT", + "type": "pure-token", + "token": "CONSISTENT" + }, + { + "name": "INCONSISTENT", "type": "pure-token", - "token": "GLOBAL" + "token": "INCONSISTENT" } ] } @@ -826,6 +991,12 @@ "SLOW", "SEARCH" ], + "history": [ + [ + "1.2.0", + "Added Text Indexing and Search support" + ] + ], "arguments": [ { "key_spec_index": 0, @@ -838,36 +1009,94 @@ "type": "string" }, { - "name": "NOCONTENT", + "name": "shard-policy", + "type": "oneof", + "optional": true, + "arguments": [ + { + "name": "ALLSHARDS", + "type": "pure-token", + "token": "ALLSHARDS" + }, + { + "name": "SOMESHARDS", + "type": "pure-token", + "token": "SOMESHARDS" + } + ] + }, + { + "name": "consistency", + "type": "oneof", + "optional": true, + "arguments": [ + { + "name": "CONSISTENT", + "type": "pure-token", + "token": "CONSISTENT" + }, + { + "name": "INCONSISTENT", + "type": "pure-token", + "token": "INCONSISTENT" + } + ] + }, + { + "name": "DIALECT", + "type": "block", + "optional": true, + "arguments": [ + { + "name": "dialect_token", + "type": "pure-token", + "token": "DIALECT" + }, + { + "name": "dialect", + "type": "integer" + } + ] + }, + { + "name": "INORDER", "type": "pure-token", "optional": true, - "token": "NOCONTENT", - "description": "When present, only matching key names are returned (no fields)." + "token": "INORDER" }, { - "name": "TIMEOUT", + "name": "LIMIT", "type": "block", "optional": true, "arguments": [ { - "name": "TIMEOUT", + "name": "limit_token", "type": "pure-token", - "token": "TIMEOUT" + "token": "LIMIT" }, { - "name": "timeout_ms", + "name": "offset", + "type": "integer" + }, + { + "name": "count", "type": "integer" } ] }, + { + "name": "NOCONTENT", + "type": "pure-token", + "optional": true, + "token": "NOCONTENT" + }, { "name": "PARAMS", "type": "block", "optional": true, - "description": "Parameter substitution map used by $-placeholders in the query string.", "arguments": [ { - "name": "PARAMS", + "name": "params_token", "type": "pure-token", "token": "PARAMS" }, @@ -876,21 +1105,29 @@ "type": "integer" }, { - "name": "pairs", - "type": "string", + "name": "parameters", + "type": "block", "multiple": true, - "description": "name/value pairs (count must be even)" + "arguments": [ + { + "name": "name", + "type": "string" + }, + { + "name": "value", + "type": "string" + } + ] } ] }, { - "name": "RETURN_FIELDS", + "name": "RETURN", "type": "block", "optional": true, - "description": "Specify which fields to return and optional aliases. If count is 0 behaves like NOCONTENT.", "arguments": [ { - "name": "RETURN_TOKEN", + "name": "return_token", "type": "pure-token", "token": "RETURN" }, @@ -900,54 +1137,109 @@ }, { "name": "fields", - "type": "string", - "multiple": true + "type": "block", + "multiple": true, + "arguments": [ + { + "name": "field", + "type": "string" + }, + { + "name": "alias", + "type": "block", + "optional": true, + "arguments": [ + { + "name": "as_token", + "type": "pure-token", + "token": "AS" + }, + { + "name": "name", + "type": "string" + } + ] + } + ] } ] }, { - "name": "LIMIT", + "name": "SLOP", "type": "block", "optional": true, "arguments": [ { - "name": "LIMIT", + "name": "slop_token", "type": "pure-token", - "token": "LIMIT" + "token": "SLOP" }, { - "name": "offset", + "name": "slop", "type": "integer" + } + ] + }, + { + "name": "SORTBY", + "type": "block", + "optional": true, + "arguments": [ + { + "name": "sortby_token", + "type": "pure-token", + "token": "SORTBY" }, { - "name": "count", - "type": "integer" + "name": "field", + "type": "string" + }, + { + "name": "direction", + "type": "oneof", + "optional": true, + "arguments": [ + { + "name": "ASC", + "type": "pure-token", + "token": "ASC" + }, + { + "name": "DESC", + "type": "pure-token", + "token": "DESC" + } + ] } ] }, { - "name": "DIALECT", + "name": "TIMEOUT", "type": "block", "optional": true, "arguments": [ { - "name": "DIALECT", + "name": "timeout_token", "type": "pure-token", - "token": "DIALECT" + "token": "TIMEOUT" }, { - "name": "dialect", + "name": "timeout_ms", "type": "integer" } - ], - "description": "Specifies the query dialect (supported values validated by server)." + ] + }, + { + "name": "VERBATIM", + "type": "pure-token", + "optional": true, + "token": "VERBATIM" }, { - "name": "LOCALONLY", + "name": "WITHSORTKEYS", "type": "pure-token", "optional": true, - "token": "LOCALONLY", - "description": "If present, restricts search to local index data only." + "token": "WITHSORTKEYS" } ], "arity": -3, @@ -956,4 +1248,4 @@ "module_since": "1.0.0", "summary": "Performs a search of the specified index. The keys which match the query expression are returned" } -} \ No newline at end of file +} diff --git a/Sources/_ValkeyCommandsBuilder/SwiftString.swift b/Sources/_ValkeyCommandsBuilder/SwiftString.swift index b01045bc..fd3768a6 100644 --- a/Sources/_ValkeyCommandsBuilder/SwiftString.swift +++ b/Sources/_ValkeyCommandsBuilder/SwiftString.swift @@ -159,4 +159,5 @@ private let swiftReservedWords: Set = [ "where", "operator", "Type", + "return", ] diff --git a/Sources/_ValkeyCommandsBuilder/ValkeyCommandJSON.swift b/Sources/_ValkeyCommandsBuilder/ValkeyCommandJSON.swift index ab7067a9..82d08a2b 100644 --- a/Sources/_ValkeyCommandsBuilder/ValkeyCommandJSON.swift +++ b/Sources/_ValkeyCommandsBuilder/ValkeyCommandJSON.swift @@ -8,7 +8,7 @@ import Foundation final class ValkeyCommands: Decodable { - let commands: [String: ValkeyCommand] + var commands: [String: ValkeyCommand] init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() @@ -36,7 +36,7 @@ struct ValkeyCommand: Decodable { let optional: Bool let token: String? let multipleToken: Bool - let arguments: [Argument]? + var arguments: [Argument]? let keySpecIndex: Int? init(from decoder: any Decoder) throws { @@ -58,10 +58,72 @@ struct ValkeyCommand: Decodable { token = "" } self.token = token - self.arguments = try container.decodeIfPresent([Argument].self, forKey: .arguments) + if let arguments = try container.decodeIfPresent([InternalArgument].self, forKey: .arguments) { + self.arguments = Self.processArguments(arguments, keySpecs: nil) + } else { + self.arguments = nil + } self.keySpecIndex = try container.decodeIfPresent(Int.self, forKey: .keySpecIndex) } + static func processArguments(_ arguments: [InternalArgument], keySpecs: [KeySpec]?) -> [Argument] { + var arguments = arguments.map { Argument(argument: $0, keySpec: $0.keySpecIndex.flatMap { keySpecs?[$0] }) } + if arguments.count >= 2 { + var index = arguments.startIndex + var prevIndex = index + index += 1 + while index != arguments.endIndex { + if arguments[prevIndex].type == .integer, arguments[prevIndex].name == "count", arguments[index].multiple == true { + arguments[index].combinedWithCount = .itemCount + } + index += 1 + prevIndex += 1 + } + } + + // combine argument and keyspec + // remove counts for arrays flagged with `combinedWithCount` + var index = arguments.startIndex + while let arrayIndex = arguments[index...].firstIndex(where: { $0.combinedWithCount != .none }) { + let previousIndex = arguments.index(before: arrayIndex) + arguments.remove(at: previousIndex) + index = arrayIndex + } + return arguments.map { argument in + guard argument.type == .block else { return argument } + guard let arguments = argument.arguments else { return argument } + switch arguments.count { + case 1: + // Collapse blocks that consist of one argument into that argument + guard argument.token == nil || arguments[0].token == nil else { return argument } + var newArgument = arguments[0] + newArgument.name = argument.name + newArgument.token = argument.token ?? newArgument.token + newArgument.optional = argument.optional || newArgument.optional + newArgument.multiple = argument.multiple || newArgument.multiple + newArgument.multipleToken = argument.multipleToken || newArgument.multipleToken + return newArgument + case 2: + // Collapse blocks that consist of a pure token and one other single argument into + // a none block type with a token attribute + guard argument.token == nil else { return argument } + guard arguments[0].type == .pureToken else { return argument } + guard arguments[1].optional == false else { return argument } + guard arguments[1].token == nil else { return argument } + guard !(arguments[1].multiple && argument.multiple) else { return argument } + var newArgument = arguments[1] + newArgument.name = argument.name + newArgument.token = arguments[0].token + newArgument.optional = argument.optional + newArgument.multiple = argument.multiple || newArgument.multiple + newArgument.multipleToken = argument.multiple + return newArgument + default: + return argument + } + } + } + private enum CodingKeys: String, CodingKey { case name case type @@ -74,14 +136,40 @@ struct ValkeyCommand: Decodable { } } struct Argument: Decodable { - let name: String - let type: ArgumentType - let multiple: Bool - let optional: Bool - let multipleToken: Bool - let token: String? - let arguments: [Argument]? - let combinedWithCount: Bool? + enum ArrayCount: String, Decodable { + case none + case parameterCount + case itemCount + } + + init( + name: String, + type: ValkeyCommand.ArgumentType, + multiple: Bool = false, + optional: Bool = false, + multipleToken: Bool = false, + token: String? = nil, + arguments: [ValkeyCommand.Argument]? = nil, + combinedWithCount: ArrayCount = .none + ) { + self.name = name + self.type = type + self.multiple = multiple + self.optional = optional + self.multipleToken = multipleToken + self.token = token + self.arguments = arguments + self.combinedWithCount = combinedWithCount + } + + var name: String + var type: ArgumentType + var multiple: Bool + var optional: Bool + var multipleToken: Bool + var token: String? + var arguments: [Argument]? + var combinedWithCount: ArrayCount init(argument: InternalArgument, keySpec: KeySpec?) { self.name = argument.name @@ -93,9 +181,9 @@ struct ValkeyCommand: Decodable { self.arguments = argument.arguments self.combinedWithCount = switch keySpec?.findKeys { - case .keynum: true - case .range, .unknown: false - case .none: false + case .keynum: .itemCount + case .range, .unknown: .none + case .none: .none } } @@ -118,8 +206,12 @@ struct ValkeyCommand: Decodable { token = "" } self.token = token - self.arguments = try container.decodeIfPresent([Argument].self, forKey: .arguments) - self.combinedWithCount = false + if let arguments = try container.decodeIfPresent([InternalArgument].self, forKey: .arguments) { + self.arguments = InternalArgument.processArguments(arguments, keySpecs: nil) + } else { + self.arguments = nil + } + self.combinedWithCount = .none } private enum CodingKeys: String, CodingKey { @@ -163,10 +255,10 @@ struct ValkeyCommand: Decodable { case null case unknown } - let description: String? - let type: ResponseType - let const: Const? - let items: [ReplySchema]? + var description: String? + var type: ResponseType + var const: Const? + var items: [ReplySchema]? init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -304,19 +396,19 @@ struct ValkeyCommand: Decodable { case findKeys = "find_keys" } } - let summary: String - let since: String? - let group: String - let complexity: String? - let function: String? - let history: [[String]]? - let deprecatedSince: String? - let replacedBy: String? - let docFlags: [String]? - let commandFlags: [String]? - let aclCategories: [String]? - let arguments: [Argument]? - let replySchema: ReplySchema? + var summary: String + var since: String? + var group: String + var complexity: String? + var function: String? + var history: [[String]]? + var deprecatedSince: String? + var replacedBy: String? + var docFlags: [String]? + var commandFlags: [String]? + var aclCategories: [String]? + var arguments: [Argument]? + var replySchema: ReplySchema? init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -332,20 +424,8 @@ struct ValkeyCommand: Decodable { self.commandFlags = try container.decodeIfPresent([String].self, forKey: .commandFlags) self.aclCategories = try container.decodeIfPresent([String].self, forKey: .aclCategories) if let arguments = try container.decodeIfPresent([InternalArgument].self, forKey: .arguments) { - if let keySpecs = try container.decodeIfPresent([KeySpec].self, forKey: .keySpecs) { - // combine argument and keyspec - var arguments = arguments.map { Argument(argument: $0, keySpec: $0.keySpecIndex.map { keySpecs[$0] }) } - // remove array counts before arrays - if let index = arguments.firstIndex(where: { $0.combinedWithCount == true }) { - let previousIndex = arguments.index(before: index) - arguments.remove(at: previousIndex) - self.arguments = arguments - } else { - self.arguments = arguments - } - } else { - self.arguments = arguments.map { .init(argument: $0, keySpec: nil) } - } + let keySpecs = try container.decodeIfPresent([KeySpec].self, forKey: .keySpecs) + self.arguments = InternalArgument.processArguments(arguments, keySpecs: keySpecs) } else { self.arguments = nil } diff --git a/Sources/_ValkeyCommandsBuilder/ValkeyCommandsRender.swift b/Sources/_ValkeyCommandsBuilder/ValkeyCommandsRender.swift index e59a73db..d22be397 100644 --- a/Sources/_ValkeyCommandsBuilder/ValkeyCommandsRender.swift +++ b/Sources/_ValkeyCommandsBuilder/ValkeyCommandsRender.swift @@ -351,7 +351,7 @@ extension String { ) } else { self.append( - "\(tab) case .\(arg.swiftArgument)(let \(arg.swiftArgument)): \(arg.respRepresentable(isArray: false, genericString: false)).respEntries\n" + "\(tab) case .\(arg.swiftArgument)(let \(arg.swiftArgument)): \(arg.respRepresentable(isArray: true, genericString: false)).respEntries\n" ) } } @@ -373,7 +373,7 @@ extension String { ) } else { self.append( - "\(tab) case .\(arg.swiftArgument)(let \(arg.swiftArgument)): \(arg.respRepresentable(isArray: false, genericString: false)).encode(into: &commandEncoder)\n" + "\(tab) case .\(arg.swiftArgument)(let \(arg.swiftArgument)): \(arg.respRepresentable(isArray: true, genericString: false)).encode(into: &commandEncoder)\n" ) } } @@ -395,7 +395,7 @@ extension String { if case .oneOf = arg.type { self.appendOneOfEnum(argument: arg, names: names, tab: tab) } else if case .block = arg.type { - self.appendBlock(argument: arg, names: names, tab: tab, genericStrings: genericStrings) + self.appendBlock(argument: arg, names: names, tab: tab, genericStrings: genericStrings && !arg.optional) } } self.append("\(tab) public struct \(blockName): RESPRenderable, Sendable, Hashable {\n") @@ -419,7 +419,7 @@ extension String { self.append("\(tab) public var respEntries: Int {\n") self.append("\(tab) ") let entries = arguments.map { - "\($0.respRepresentable(isArray: false, genericString: genericStrings)).respEntries" + "\($0.respRepresentable(isArray: true, genericString: genericStrings)).respEntries" } self.append(entries.joined(separator: " + ")) self.append("\n") @@ -428,7 +428,7 @@ extension String { self.append("\(tab) public func encode(into commandEncoder: inout ValkeyCommandEncoder) {\n") for arg in arguments { self.append( - "\(tab) \(arg.respRepresentable(isArray: false, genericString: genericStrings)).encode(into: &commandEncoder)\n" + "\(tab) \(arg.respRepresentable(isArray: true, genericString: genericStrings)).encode(into: &commandEncoder)\n" ) } self.append("\(tab) }\n") @@ -586,10 +586,7 @@ extension String { ) self.append(" }\n\n") } - //_appendFunction(isArray: false) - //if arguments.contains(where: \.multiple) { _appendFunction(isArray: true) - //} } } @@ -901,25 +898,27 @@ extension ValkeyCommand.Argument { variable = "RESPRenderableBulkString(\(variable))" } } - if let token = self.token { + return if let token = self.token { if self.multiple, self.multipleToken { - return "RESPArrayWithToken(\"\(token)\", \(variable))" + "RESPArrayWithToken(\"\(token)\", \(variable))" } else { - return "RESPWithToken(\"\(token)\", \(variable))" - } - } else if self.multiple, self.combinedWithCount == true { - if isArray { - return "RESPArrayWithCount(\(variable))" - } else { - return "1, \(variable)" + switch (self.multiple, self.combinedWithCount) { + case (true, .itemCount): "RESPArrayWithTokenAndCount(\"\(token)\", \(variable))" + case (true, .parameterCount): "RESPArrayWithTokenAndParameterCount(\"\(token)\", \(variable))" + default: "RESPWithToken(\"\(token)\", \(variable))" + } } } else { - return variable + switch (self.multiple, self.combinedWithCount) { + case (true, .itemCount): "RESPArrayWithCount(\(variable))" + case (true, .parameterCount): "RESPArrayWithParameterCount(\(variable))" + default: variable + } } } } - // return if argument can be configurated by parameters + // return if argument can be configured by parameters func hasParameters() -> Bool { switch self.type { case .pureToken: @@ -946,7 +945,7 @@ extension ValkeyCommand.Argument { /// Name we use for rendered variable. Appends an "s" for array arguments var renderedName: String { if self.multiple { - if self.name == "data" || self.name.last == "s" { + if self.name == "data" || self.name.last == "s" || self.name.last == "S" { name } else if self.name.last == "y", self.name.dropLast().last != "e" { "\(self.name.dropLast())ies" diff --git a/Sources/_ValkeyCommandsBuilder/app.swift b/Sources/_ValkeyCommandsBuilder/app.swift index b680e959..7a081ffb 100755 --- a/Sources/_ValkeyCommandsBuilder/app.swift +++ b/Sources/_ValkeyCommandsBuilder/app.swift @@ -26,6 +26,7 @@ struct App { let jsonCommands = try load(fileURL: resourceFolder.appending(path: "valkey-json-commands.json"), as: ValkeyCommands.self) try writeValkeyCommands(toFolder: "Sources/ValkeyJSON/", commands: jsonCommands, module: true) let searchCommands = try load(fileURL: resourceFolder.appending(path: "valkey-search-commands.json"), as: ValkeyCommands.self) + try searchCommands.patch(.searchPatches) try writeValkeyCommands(toFolder: "Sources/ValkeySearch/", commands: searchCommands, module: true) } } diff --git a/Sources/_ValkeyCommandsBuilder/patch.swift b/Sources/_ValkeyCommandsBuilder/patch.swift new file mode 100644 index 00000000..4f5cabe6 --- /dev/null +++ b/Sources/_ValkeyCommandsBuilder/patch.swift @@ -0,0 +1,172 @@ +// +// This source file is part of the valkey-swift project +// Copyright (c) 2025 the valkey-swift project authors +// +// See LICENSE.txt for license information +// SPDX-License-Identifier: Apache-2.0 +// + +struct ValkeyPatchError: Error {} + +/// Patch to apply to argument +protocol ArgumentPatch { + func apply(to: ValkeyCommand.Argument) -> ValkeyCommand.Argument +} + +/// Patch that replaces argument +struct ReplaceArgumentPatch: ArgumentPatch { + let replacement: ValkeyCommand.Argument + + func apply(to: ValkeyCommand.Argument) -> ValkeyCommand.Argument { + replacement + } +} + +extension ArgumentPatch where Self == ReplaceArgumentPatch { + static func replace(_ replacement: ValkeyCommand.Argument) -> ReplaceArgumentPatch { + .init(replacement: replacement) + } +} + +/// Patch that sets a single field in the parameter +struct SetFieldPatch: ArgumentPatch { + let keyPath: WritableKeyPath + let value: Value + + func apply(to original: ValkeyCommand.Argument) -> ValkeyCommand.Argument { + var argument = original + argument[keyPath: keyPath] = value + return argument + } +} + +extension ArgumentPatch where Self == SetFieldPatch { + static func set(_ keyPath: WritableKeyPath, value: String) -> SetFieldPatch { + .init(keyPath: keyPath, value: value) + } +} + +extension ArgumentPatch where Self == SetFieldPatch { + static func set(_ keyPath: WritableKeyPath, value: Self) -> SetFieldPatch { + .init(keyPath: keyPath, value: value) + } +} + +extension ArgumentPatch where Self == SetFieldPatch { + static func set( + _ keyPath: WritableKeyPath, + value: ValkeyCommand.Argument.ArrayCount + ) -> SetFieldPatch { + .init(keyPath: keyPath, value: value) + } +} + +/// Patch applied to command. +struct CommandPatch { + /// command name + let command: String + /// argument path + let path: [String] + /// patch to apply + let patch: any ArgumentPatch + + init(_ command: String, path: [String], patch: any ArgumentPatch) { + self.command = command + self.path = path + self.patch = patch + } +} + +extension ValkeyCommand { + /// Patch arguments of command + mutating func patchArguments(_ path: [String], patch: some ArgumentPatch) throws { + guard let arguments = self.arguments else { throw ValkeyPatchError() } + guard let index = self.arguments?.firstIndex(where: { $0.name == path.first }) else { throw ValkeyPatchError() } + if path.count == 1 { + self.arguments?[index] = patch.apply(to: arguments[index]) + } else { + try self.arguments?[index].patch(path.dropFirst(), patch: patch) + } + } +} + +extension ValkeyCommand.Argument { + /// Patch arguments of argument + mutating func patch(_ path: ArraySlice, patch: some ArgumentPatch) throws { + guard let arguments = self.arguments else { throw ValkeyPatchError() } + guard let index = self.arguments?.firstIndex(where: { $0.name == path.first }) else { throw ValkeyPatchError() } + if path.count == 1 { + self.arguments?[index] = patch.apply(to: arguments[index]) + } else { + try self.arguments?[index].patch(path.dropFirst(), patch: patch) + } + } +} + +extension ValkeyCommands { + /// Patch commands + func patch(_ patches: [CommandPatch]) throws { + for patch in patches { + try self.patch(patch.command, path: patch.path, patch: patch.patch) + } + } + + /// Patch a single command + func patch(_ command: String, path: [String], patch: some ArgumentPatch) throws { + guard self.commands[command] != nil else { throw ValkeyPatchError() } + try self.commands[command]?.patchArguments(path, patch: patch) + } +} + +extension [CommandPatch] { + /// Patches applied to ValkeySearch module + static var searchPatches: [CommandPatch] { + [ + // FT.AGGREGATE sort parameters are not grouped into expression and direction + .init( + "FT.AGGREGATE", + path: ["SORTBY", "sort_params"], + patch: .replace( + .init( + name: "expression", + type: .block, + multiple: true, + arguments: [ + .init(name: "expression", type: .string), + .init( + name: "direction", + type: .oneOf, + optional: true, + arguments: [ + .init(name: "ASC", type: .pureToken, token: "ASC"), + .init(name: "DESC", type: .pureToken, token: "DESC"), + ] + ), + ], + combinedWithCount: .parameterCount + ) + ) + ), + // params field and value are counted separately + .init("FT.AGGREGATE", path: ["PARAMS"], patch: .set(\.combinedWithCount, value: .parameterCount)), + // params field and value are counted separately + .init("FT.SEARCH", path: ["PARAMS"], patch: .set(\.combinedWithCount, value: .parameterCount)), + // FT.CREATE vector type should be an enum of the different kinds even though there + // is only one type at the moment + .init( + "FT.CREATE", + path: ["schema", "field-type", "VECTOR", "vector-params", "type"], + patch: .replace( + .init( + name: "type", + type: .oneOf, + token: "TYPE", + arguments: [ + .init(name: "float32", type: .pureToken, token: "FLOAT32") + ] + ) + ) + ), + ] + } +} diff --git a/Tests/ValkeyTests/CommandTests.swift b/Tests/ValkeyTests/CommandTests.swift index 2f3859a2..3a247683 100644 --- a/Tests/ValkeyTests/CommandTests.swift +++ b/Tests/ValkeyTests/CommandTests.swift @@ -276,6 +276,20 @@ struct CommandTests { } struct ScriptCommands { + @Test + @available(valkeySwift 1.0, *) + func eval() async throws { + try await testCommandEncodesDecodes( + ( + request: .command(["EVAL", "return ARGV[1]", "0", "hello"]), + response: .bulkString("hello") + ) + ) { connection in + let response = try await connection.eval(script: "return ARGV[1]", args: ["hello"]) + try #expect(response.decode(as: String.self) == "hello") + } + } + @Test @available(valkeySwift 1.0, *) func functionList() async throws { @@ -1717,7 +1731,7 @@ struct CommandTests { let result = try await connection.ftSearch( index: "idx:myIndex", query: "@title:Hello", - timeout: .init(timeoutMs: 100) + timeout: 100 ) guard case .array(let arr1) = result.value else { @@ -1738,7 +1752,7 @@ struct CommandTests { @Test @available(valkeySwift 1.0, *) - func ftSearch_paramsAndLocalOnly() async throws { + func ftSearch_params() async throws { try await testCommandEncodesDecodes( ( request: .command([ @@ -1748,7 +1762,6 @@ struct CommandTests { "PARAMS", "4", "t", "Hello", "tag", "world", - "LOCALONLY", ]), response: .array([ .number(0) @@ -1758,11 +1771,7 @@ struct CommandTests { let result = try await connection.ftSearch( index: "idx:myIndex", query: "@title:$t @tag:{$tag}", - params: .init( - count: 4, - pairs: ["t", "Hello", "tag", "world"] - ), - localonly: true + params: [.init(name: "t", value: "Hello"), .init(name: "tag", value: "world")] ) guard case .array(let arr1) = result.value else { @@ -1808,10 +1817,7 @@ struct CommandTests { let result = try await connection.ftSearch( index: "idx:myIndex", query: "@title:Hello", - returnFields: .init( - count: 2, - fields: ["title", "body"] - ) + returns: [.init(field: "title"), .init(field: "body")] ) guard case .array(let arr1) = result.value else { @@ -1862,9 +1868,9 @@ struct CommandTests { "FT.SEARCH", "idx:myIndex", "@title:Hello", - "NOCONTENT", - "LIMIT", "0", "5", "DIALECT", "2", + "LIMIT", "0", "5", + "NOCONTENT", ]), response: .array([ .number(0) @@ -1874,9 +1880,9 @@ struct CommandTests { let result = try await connection.ftSearch( index: "idx:myIndex", query: "@title:Hello", - nocontent: true, + dialect: 2, limit: .init(offset: 0, count: 5), - dialect: .init(dialect: 2) + nocontent: true ) guard case .array(let arr1) = result.value else { @@ -1986,7 +1992,7 @@ struct CommandTests { request: .command([ "FT.AGGREGATE", "idx:testIndex", "*", "LOAD", "1", - "@title", "AS", "title", + "@title", ]), response: .array([.number(0)]) ) @@ -1994,11 +2000,8 @@ struct CommandTests { let result = try await connection.ftAggregate( index: "idx:testIndex", query: "*", - load: .init( - nargs: 1, - items: [ - .init(identifier: "@title", alias: .init(property: "title")) - ] + load: .fields( + ["@title"] ) ) @@ -2024,7 +2027,7 @@ struct CommandTests { request: .command([ "FT.AGGREGATE", "idx:testIndex", "*", "LOAD", "2", - "@title", "AS", "title", + "@title", "@body", ]), response: .array([.number(0)]) @@ -2033,12 +2036,8 @@ struct CommandTests { let result = try await connection.ftAggregate( index: "idx:testIndex", query: "*", - load: .init( - nargs: 2, - items: [ - .init(identifier: "@title", alias: .init(property: "title")), - .init(identifier: "@body"), - ] + load: .fields( + ["@title", "@body"] ) ) @@ -2071,7 +2070,7 @@ struct CommandTests { let result = try await connection.ftAggregate( index: "idx:testIndex", query: "*", - timeout: .init(milliseconds: 100) + timeout: 100 ) guard case .array(let arr) = result.value else { @@ -2105,7 +2104,7 @@ struct CommandTests { let result = try await connection.ftAggregate( index: "idx:testIndex", query: "@loc:[$lon $lat 10 km]", - params: .init(nargs: 4, parameters: [.init(name: "lon", value: "29.69465"), .init(name: "lat", value: "34.95126")]) + params: [.init(name: "lon", value: "29.69465"), .init(name: "lat", value: "34.95126")] ) guard case .array(let arr) = result.value else { @@ -2137,41 +2136,7 @@ struct CommandTests { let result = try await connection.ftAggregate( index: "idx:testIndex", query: "*", - dialect: .init(dialectVersion: 2) - ) - - guard case .array(let arr) = result.value else { - Issue.record("Expected array") - return - } - let items = Array(arr) - #expect(items.count == 1) - guard case .number(let n) = items[0].value else { - Issue.record("Expected number") - return - } - #expect(n == 0) - } - } - - @Test - @available(valkeySwift 1.0, *) - func ftAggregate_scorer_addscores() async throws { - try await testCommandEncodesDecodes( - ( - request: .command([ - "FT.AGGREGATE", "idx:testIndex", "hello", - "SCORER", "BM25", - "ADDSCORES", - ]), - response: .array([.number(0)]) - ) - ) { connection in - let result = try await connection.ftAggregate( - index: "idx:testIndex", - query: "hello", - scorer: .init(scorer: "BM25"), - addscores: true + dialect: 2 ) guard case .array(let arr) = result.value else { @@ -2196,7 +2161,6 @@ struct CommandTests { request: .command([ "FT.AGGREGATE", "idx:testIndex", "hello", "SORTBY", "4", "@foo", "ASC", "@bar", "DESC", - "WITHCOUNT", ]), response: .array([.number(0)]) ) @@ -2205,9 +2169,10 @@ struct CommandTests { index: "idx:testIndex", query: "hello", sortby: .init( - nargs: 4, - sortParams: ["@foo", "ASC", "@bar", "DESC"], - withcount: true + expressions: [ + .init(expression: "@foo", direction: .asc), + .init(expression: "@bar", direction: .desc), + ] ) ) @@ -2242,9 +2207,8 @@ struct CommandTests { index: "idx:testIndex", query: "hello", sortby: .init( - nargs: 2, - sortParams: ["@foo", "DESC"], - max: .init(num: 100) + expressions: [.init(expression: "@foo", direction: .desc)], + max: 100 ) ) @@ -2279,8 +2243,8 @@ struct CommandTests { index: "idx:testIndex", query: "*", applys: [ - .init(expr: "sqrt(@foo)", name: "foo_sqrt"), - .init(expr: "(@bar*2)", name: "bar2"), + .init(expression: "sqrt(@foo)", field: "foo_sqrt"), + .init(expression: "(@bar*2)", field: "bar2"), ] ) @@ -2313,7 +2277,7 @@ struct CommandTests { let result = try await connection.ftAggregate( index: "idx:testIndex", query: "*", - limit: .init(offset: 10, num: 20) + limits: [.init(offset: 10, count: 20)] ) guard case .array(let arr) = result.value else { @@ -2346,47 +2310,7 @@ struct CommandTests { let result = try await connection.ftAggregate( index: "idx:testIndex", query: "*", - filters: [ - .init(expr: "@foo > 10"), - .init(expr: "@bar < 20"), - ] - ) - - guard case .array(let arr) = result.value else { - Issue.record("Expected array") - return - } - let items = Array(arr) - #expect(items.count == 1) - guard case .number(let n) = items[0].value else { - Issue.record("Expected number") - return - } - #expect(n == 0) - } - } - - @Test - @available(valkeySwift 1.0, *) - func ftAggregate_withcursor_count_maxidle() async throws { - try await testCommandEncodesDecodes( - ( - request: .command([ - "FT.AGGREGATE", "idx:testIndex", "*", - "WITHCURSOR", - "COUNT", "500", - "MAXIDLE", "10000", - ]), - response: .array([.number(0)]) - ) - ) { connection in - let result = try await connection.ftAggregate( - index: "idx:testIndex", - query: "*", - withcursor: .init( - count: .init(readSize: 500), - maxidle: .init(idleTime: 10000) - ) + filters: ["@foo > 10", "@bar < 20"] ) guard case .array(let arr) = result.value else { @@ -2421,19 +2345,17 @@ struct CommandTests { query: "*", groupbys: [ .init( - nargs: 1, - groupFields: ["@category"], + fields: ["@category"], + reduces: [ + .init( + function: .sum, + expressions: ["@price"], + alias: "total_revenue" + ) + ] ) ], - reduces: [ - .init( - function: .sum, - nargs: 1, - identifiers: ["@price"], - alias: .init(identifier: "total_revenue") - ) - ] ) guard case .array(let arr) = result.value else { @@ -2468,19 +2390,17 @@ struct CommandTests { query: "*", groupbys: [ .init( - nargs: 2, - groupFields: ["@category", "@brand"], + fields: ["@category", "@brand"], + reduces: [ + .init( + function: .countDistinct, + expressions: ["@user"], + alias: "uniq_users" + ) + ] ) ], - reduces: [ - .init( - function: .countDistinct, - nargs: 1, - identifiers: ["@user"], - alias: .init(identifier: "uniq_users") - ) - ] ) guard case .array(let arr) = result.value else { @@ -2515,11 +2435,11 @@ struct CommandTests { ) { connection in try await connection.ftCreate( indexName: "idx:testIndex", - on: .init(type: .json), - prefix: .init(count: 1, prefixes: ["item:"]), - schema: .init(fields: [ - .init(fieldIdentifier: "$.name", alias: .init(fieldIdentifier: "name"), fieldType: .text) - ]) + on: .json, + prefixes: ["item:"], + schemas: [ + .init(fieldIdentifier: "$.name", alias: "name", fieldType: .text(.init())) + ] ) } } @@ -2541,14 +2461,12 @@ struct CommandTests { try await connection.ftCreate( indexName: "idx:noOn", on: nil, - prefix: nil, - schema: .init(fields: [ + schemas: [ .init( fieldIdentifier: "age", - alias: nil, fieldType: .numeric ) - ]) + ] ) } } @@ -2569,20 +2487,17 @@ struct CommandTests { ) { connection in try await connection.ftCreate( indexName: "idx:tagSepOnly", - on: nil, - prefix: nil, - schema: .init(fields: [ + schemas: [ .init( fieldIdentifier: "category", - alias: nil, fieldType: .tag( .init( - separator: .init(sep: "|"), + separator: "|", casesensitive: false ) ) ) - ]) + ] ) } } @@ -2603,20 +2518,16 @@ struct CommandTests { ) { connection in try await connection.ftCreate( indexName: "idx:tagCaseOnly", - on: nil, - prefix: nil, - schema: .init(fields: [ + schemas: [ .init( fieldIdentifier: "category", - alias: nil, fieldType: .tag( .init( - separator: nil, casesensitive: true ) ) ) - ]) + ] ) } } @@ -2638,15 +2549,13 @@ struct CommandTests { ) { connection in try await connection.ftCreate( indexName: "idx:hashIndex", - on: .init(type: .hash), - prefix: nil, - schema: .init(fields: [ + on: .hash, + schemas: [ .init( fieldIdentifier: "name", - alias: nil, - fieldType: .text + fieldType: .text(.init()) ) - ]) + ] ) } } @@ -2668,20 +2577,17 @@ struct CommandTests { ) { connection in try await connection.ftCreate( indexName: "idx:tagIndex", - on: .init(type: .hash), - prefix: nil, - schema: .init(fields: [ + on: .hash, + schemas: [ .init( fieldIdentifier: "category", - alias: nil, fieldType: .tag( .init( - separator: nil, casesensitive: false ) ) ) - ]) + ] ) } } @@ -2703,20 +2609,18 @@ struct CommandTests { ) { connection in try await connection.ftCreate( indexName: "idx:tagIndex2", - on: .init(type: .hash), - prefix: nil, - schema: .init(fields: [ + on: .hash, + schemas: [ .init( fieldIdentifier: "category", - alias: nil, fieldType: .tag( .init( - separator: .init(sep: "|"), + separator: "|", casesensitive: true ) ) ) - ]) + ] ) } } @@ -2739,15 +2643,14 @@ struct CommandTests { ) { connection in try await connection.ftCreate( indexName: "idx:multiPrefix", - on: .init(type: .hash), - prefix: .init(count: 2, prefixes: ["item:", "product:"]), - schema: .init(fields: [ + on: .hash, + prefixes: ["item:", "product:"], + schemas: [ .init( fieldIdentifier: "name", - alias: nil, - fieldType: .text + fieldType: .text(.init()) ) - ]) + ] ) } } @@ -2770,20 +2673,19 @@ struct CommandTests { ) { connection in try await connection.ftCreate( indexName: "idx:multiField", - on: .init(type: .hash), - prefix: nil, - schema: .init(fields: [ + on: .hash, + schemas: [ .init( fieldIdentifier: "name", alias: nil, - fieldType: .text + fieldType: .text(.init()) ), .init( fieldIdentifier: "age", alias: nil, fieldType: .numeric ), - ]) + ] ) } } @@ -2806,20 +2708,19 @@ struct CommandTests { "TYPE", "FLOAT32", "DIM", "128", "DISTANCE_METRIC", "L2", - "BLOCK_SIZE", "1024", ]), response: .simpleString("OK") ) ) { connection in try await connection.ftCreate( indexName: "idx:mixed", - on: .init(type: .hash), - prefix: .init(count: 2, prefixes: ["item:", "product:"]), - schema: .init(fields: [ + on: .hash, + prefixes: ["item:", "product:"], + schemas: [ .init( fieldIdentifier: "name", alias: nil, - fieldType: .text + fieldType: .text(.init()) ), .init( fieldIdentifier: "price", @@ -2831,7 +2732,7 @@ struct CommandTests { alias: nil, fieldType: .tag( .init( - separator: .init(sep: "|"), + separator: "|", casesensitive: false ) ) @@ -2844,17 +2745,14 @@ struct CommandTests { algorithm: .flat, attrCount: 4, vectorParams: .init( - type: .init(), - dim: .init(value: 128), - distanceMetric: .init(metric: .l2), - m: nil, - efConstruction: nil, - blockSize: .init(value: 1024) + type: .float32, + dim: 128, + distanceMetric: .l2 ) ) ) ), - ]) + ] ) } } @@ -2884,8 +2782,7 @@ struct CommandTests { try await connection.ftCreate( indexName: "my_index_name", on: nil, - prefix: nil, - schema: .init(fields: [ + schemas: [ .init( fieldIdentifier: "my_hash_field_key", alias: nil, @@ -2894,16 +2791,16 @@ struct CommandTests { algorithm: .hnsw, attrCount: 10, vectorParams: .init( - type: .init(), - dim: .init(value: 20), - distanceMetric: .init(metric: .cosine), - m: .init(value: 4), - efConstruction: .init(value: 100) + type: .float32, + dim: 20, + distanceMetric: .cosine, + m: 4, + efConstruction: 100 ) ) ) ) - ]) + ] ) } } @@ -2924,7 +2821,6 @@ struct CommandTests { "TYPE", "FLOAT32", "DIM", "128", "DISTANCE_METRIC", "L2", - "BLOCK_SIZE", "1024", ]), response: .simpleString("OK") ) @@ -2932,8 +2828,7 @@ struct CommandTests { try await connection.ftCreate( indexName: "my_flat_index", on: nil, - prefix: nil, - schema: .init(fields: [ + schemas: [ .init( fieldIdentifier: "embedding", alias: nil, @@ -2942,15 +2837,14 @@ struct CommandTests { algorithm: .flat, attrCount: 4, vectorParams: .init( - type: .init(), - dim: .init(value: 128), - distanceMetric: .init(metric: .l2), - blockSize: .init(value: 1024) + type: .float32, + dim: 128, + distanceMetric: .l2 ) ) ) ) - ]) + ] ) } } @@ -2977,28 +2871,22 @@ struct CommandTests { ) { connection in try await connection.ftCreate( indexName: "idx:flatIP", - on: nil, - prefix: nil, - schema: .init(fields: [ + schemas: [ .init( fieldIdentifier: "embedding", - alias: nil, fieldType: .vector( .init( algorithm: .flat, attrCount: 8, vectorParams: .init( - type: .init(), - dim: .init(value: 64), - distanceMetric: .init(metric: .ip), - m: nil, - efConstruction: nil, - blockSize: nil + type: .float32, + dim: 64, + distanceMetric: .ip ) ) ) ) - ]) + ] ) } } @@ -3012,7 +2900,7 @@ struct CommandTests { response: .simpleString("OK") ) ) { connection in - try await connection.ftDropindex("idx:myIndex") + try await connection.ftDropindex(indexName: "idx:myIndex") } } @@ -3041,7 +2929,7 @@ struct CommandTests { ]) ), ( - request: .command(["FT.INFO", "idx:myIndex", "GLOBAL"]), + request: .command(["FT.INFO", "idx:myIndex", "PRIMARY"]), response: .array([ .bulkString("index_name"), .bulkString("idx:myIndex"), @@ -3050,9 +2938,9 @@ struct CommandTests { ]) ) ) { connection in - _ = try await connection.ftInfo("idx:myIndex") - _ = try await connection.ftInfo("idx:myIndex", scope: .local) - _ = try await connection.ftInfo("idx:myIndex", scope: .global) + _ = try await connection.ftInfo(indexName: "idx:myIndex") + _ = try await connection.ftInfo(indexName: "idx:myIndex", scope: .local) + _ = try await connection.ftInfo(indexName: "idx:myIndex", scope: .primary) } }