Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
StackExchange.Redis
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
tsai
StackExchange.Redis
Commits
eba20f84
Commit
eba20f84
authored
May 02, 2017
by
Marc Gravell
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
NRediSearch => SE.Redis.Modules; implement CL.THROTTLE
parent
69897ca9
Changes
9
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
1014 additions
and
1 deletion
+1014
-1
NRediSearch.csproj
NRediSearch/NRediSearch.csproj
+1
-1
Client.cs
StackExchange.Redis.Modules/RediSearch/Client.cs
+420
-0
Document.cs
StackExchange.Redis.Modules/RediSearch/Document.cs
+51
-0
Literals.cs
StackExchange.Redis.Modules/RediSearch/Literals.cs
+36
-0
Query.cs
StackExchange.Redis.Modules/RediSearch/Query.cs
+264
-0
Schema.cs
StackExchange.Redis.Modules/RediSearch/Schema.cs
+103
-0
SearchResult.cs
StackExchange.Redis.Modules/RediSearch/SearchResult.cs
+75
-0
StackExchange.Redis.Modules.csproj
...Exchange.Redis.Modules/StackExchange.Redis.Modules.csproj
+12
-0
Throttling.cs
StackExchange.Redis.Modules/Throttling.cs
+52
-0
No files found.
NRediSearch/NRediSearch.csproj
View file @
eba20f84
...
...
@@ -4,7 +4,7 @@
<TargetFrameworks>$(LibraryTargetFrameworks)</TargetFrameworks>
<VersionPrefix>0.1</VersionPrefix>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<PackageTags>Redis;Search;RediSearch</PackageTags>
<PackageTags>Redis;Search;
Modules;
RediSearch</PackageTags>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StackExchange.Redis\StackExchange.Redis.csproj" />
...
...
StackExchange.Redis.Modules/RediSearch/Client.cs
0 → 100644
View file @
eba20f84
This diff is collapsed.
Click to expand it.
StackExchange.Redis.Modules/RediSearch/Document.cs
0 → 100644
View file @
eba20f84
// .NET port of https://github.com/RedisLabs/JRediSearch/
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Text
;
using
System.Threading.Tasks
;
using
StackExchange.Redis
;
namespace
StackExchange.Redis.Modules.RediSearch
{
/// <summary>
/// Document represents a single indexed document or entity in the engine
/// </summary>
public
class
Document
{
public
string
Id
{
get
;
}
public
double
Score
{
get
;
}
public
byte
[]
Payload
{
get
;
}
private
Dictionary
<
String
,
RedisValue
>
properties
=
new
Dictionary
<
string
,
RedisValue
>();
public
Document
(
string
id
,
double
score
,
byte
[]
payload
)
{
Id
=
id
;
Score
=
score
;
Payload
=
payload
;
}
public
static
Document
Load
(
string
id
,
double
score
,
byte
[]
payload
,
RedisValue
[]
fields
)
{
Document
ret
=
new
Document
(
id
,
score
,
payload
);
if
(
fields
!=
null
)
{
for
(
int
i
=
0
;
i
<
fields
.
Length
;
i
+=
2
)
{
ret
[(
string
)
fields
[
i
]]
=
fields
[
i
+
1
];
}
}
return
ret
;
}
public
RedisValue
this
[
string
key
]
{
get
{
return
properties
.
TryGetValue
(
key
,
out
var
val
)
?
val
:
default
(
RedisValue
);
}
internal
set
{
properties
[
key
]
=
value
;
}
}
public
bool
HasProperty
(
string
key
)
=>
properties
.
ContainsKey
(
key
);
}
}
StackExchange.Redis.Modules/RediSearch/Literals.cs
0 → 100644
View file @
eba20f84
using
StackExchange.Redis
;
using
System.Collections
;
namespace
StackExchange.Redis.Modules.RediSearch
{
/// <summary>
/// Cache to ensure we encode and box literals once only
/// </summary>
internal
static
class
Literals
{
private
static
Hashtable
_boxed
=
new
Hashtable
();
private
static
object
_null
=
RedisValue
.
Null
;
/// <summary>
/// Obtain a lazily-cached pre-encoded and boxed representation of a string
/// </summary>
/// <remarks>This shoul donly be used for fixed values, not user data (the cache is never reclaimed, so it will be a memory leak)</remarks>
public
static
object
Literal
(
this
string
value
)
{
if
(
value
==
null
)
return
_null
;
object
boxed
=
_boxed
[
value
];
if
(
boxed
==
null
)
{
lock
(
_boxed
)
{
boxed
=
_boxed
[
value
];
if
(
boxed
==
null
)
{
boxed
=
(
RedisValue
)
value
;
_boxed
.
Add
(
value
,
boxed
);
}
}
}
return
boxed
;
}
}
}
StackExchange.Redis.Modules/RediSearch/Query.cs
0 → 100644
View file @
eba20f84
// .NET port of https://github.com/RedisLabs/JRediSearch/
using
StackExchange.Redis
;
using
System
;
using
System.Collections.Generic
;
using
System.Globalization
;
namespace
StackExchange.Redis.Modules.RediSearch
{
/// <summary>
/// Query represents query parameters and filters to load results from the engine
/// </summary>
public
class
Query
{
/// <summary>
/// Filter represents a filtering rules in a query
/// </summary>
public
abstract
class
Filter
{
public
string
Property
{
get
;
}
internal
abstract
void
SerializeRedisArgs
(
List
<
object
>
args
);
internal
Filter
(
string
property
)
{
Property
=
property
;
}
}
/// <summary>
/// NumericFilter wraps a range filter on a numeric field. It can be inclusive or exclusive
/// </summary>
public
class
NumericFilter
:
Filter
{
private
readonly
double
min
,
max
;
private
readonly
bool
exclusiveMin
,
exclusiveMax
;
public
NumericFilter
(
string
property
,
double
min
,
bool
exclusiveMin
,
double
max
,
bool
exclusiveMax
)
:
base
(
property
)
{
this
.
min
=
min
;
this
.
max
=
max
;
this
.
exclusiveMax
=
exclusiveMax
;
this
.
exclusiveMin
=
exclusiveMin
;
}
public
NumericFilter
(
string
property
,
double
min
,
double
max
)
:
this
(
property
,
min
,
false
,
max
,
false
)
{
}
internal
override
void
SerializeRedisArgs
(
List
<
object
>
args
)
{
RedisValue
FormatNum
(
double
num
,
bool
exclude
)
{
if
(!
exclude
||
double
.
IsInfinity
(
num
))
{
return
(
RedisValue
)
num
;
// can use directly
}
// need to add leading bracket
return
"("
+
num
.
ToString
(
"G17"
,
NumberFormatInfo
.
InvariantInfo
);
}
args
.
Add
(
"FILTER"
.
Literal
());
args
.
Add
(
Property
);
args
.
Add
(
FormatNum
(
min
,
exclusiveMin
));
args
.
Add
(
FormatNum
(
max
,
exclusiveMax
));
}
}
/// <summary>
/// GeoFilter encapsulates a radius filter on a geographical indexed fields
/// </summary>
public
class
GeoFilter
:
Filter
{
private
readonly
double
lon
,
lat
,
radius
;
private
GeoUnit
unit
;
public
GeoFilter
(
string
property
,
double
lon
,
double
lat
,
double
radius
,
GeoUnit
unit
)
:
base
(
property
)
{
this
.
lon
=
lon
;
this
.
lat
=
lat
;
this
.
radius
=
radius
;
this
.
unit
=
unit
;
}
internal
override
void
SerializeRedisArgs
(
List
<
object
>
args
)
{
args
.
Add
(
"GEOFILTER"
.
Literal
());
args
.
Add
(
Property
);
args
.
Add
(
lon
);
args
.
Add
(
lat
);
args
.
Add
(
radius
);
switch
(
unit
)
{
case
GeoUnit
.
Feet
:
args
.
Add
(
"ft"
.
Literal
());
break
;
case
GeoUnit
.
Kilometers
:
args
.
Add
(
"km"
.
Literal
());
break
;
case
GeoUnit
.
Meters
:
args
.
Add
(
"m"
.
Literal
());
break
;
case
GeoUnit
.
Miles
:
args
.
Add
(
"mi"
.
Literal
());
break
;
default
:
throw
new
InvalidOperationException
(
$"Unknown unit:
{
unit
}
"
);
}
}
}
private
struct
Paging
{
public
int
Offset
{
get
;
}
public
int
Count
{
get
;
}
public
Paging
(
int
offset
,
int
count
)
{
Offset
=
offset
;
Count
=
count
;
}
}
/// <summary>
/// The query's filter list. We only support AND operation on all those filters
/// </summary>
List
<
Filter
>
_filters
=
new
List
<
Filter
>();
/// <summary>
/// The textual part of the query
/// </summary>
public
string
QueryString
{
get
;
}
/// <summary>
/// The sorting parameters
/// </summary>
Paging
_paging
=
new
Paging
(
0
,
10
);
/// <summary>
/// Set the query to verbatim mode, disabling stemming and query expansion
/// </summary>
public
bool
Verbatim
{
get
;
set
;
}
/// <summary>
/// Set the query not to return the contents of documents, and rather just return the ids
/// </summary>
public
bool
NoContent
{
get
;
set
;
}
/// <summary>
/// Set the query not to filter for stopwords. In general this should not be used
/// </summary>
public
bool
NoStopwords
{
get
;
set
;
}
/// <summary>
/// Set the query to return a factored score for each results. This is useful to merge results from multiple queries.
/// </summary>
public
bool
WithScores
{
get
;
set
;
}
/// <summary>
/// Set the query to return object payloads, if any were given
/// </summary>
public
bool
WithPayloads
{
get
;
set
;
}
/// <summary>
/// Set the query language, for stemming purposes; see http://redisearch.io for documentation on languages and stemming
/// </summary>
public
string
Language
{
get
;
set
;
}
protected
String
[]
_fields
=
null
;
/// <summary>
/// Set the query payload to be evaluated by the scoring function
/// </summary>
public
byte
[]
Payload
{
get
;
set
;
}
/// <summary>
/// Create a new index
/// </summary>
public
Query
(
String
queryString
)
{
QueryString
=
queryString
;
}
internal
void
SerializeRedisArgs
(
List
<
object
>
args
)
{
args
.
Add
(
QueryString
);
if
(
Verbatim
)
{
args
.
Add
(
"VERBATIM"
.
Literal
());
}
if
(
NoContent
)
{
args
.
Add
(
"NOCONTENT"
.
Literal
());
}
if
(
NoStopwords
)
{
args
.
Add
(
"NOSTOPWORDS"
.
Literal
());
}
if
(
WithScores
)
{
args
.
Add
(
"WITHSCORES"
.
Literal
());
}
if
(
WithPayloads
)
{
args
.
Add
(
"WITHPAYLOADS"
.
Literal
());
}
if
(
Language
!=
null
)
{
args
.
Add
(
"LANGUAGE"
.
Literal
());
args
.
Add
(
Language
);
}
if
(
_fields
!=
null
&&
_fields
.
Length
>
0
)
{
args
.
Add
(
"INFIELDS"
.
Literal
());
args
.
Add
(
_fields
.
Length
);
args
.
AddRange
(
_fields
);
}
if
(
Payload
!=
null
)
{
args
.
Add
(
"PAYLOAD"
.
Literal
());
args
.
Add
(
Payload
);
}
if
(
_paging
.
Offset
!=
0
||
_paging
.
Count
!=
10
)
{
args
.
Add
(
"LIMIT"
.
Literal
());
args
.
Add
(
_paging
.
Offset
);
args
.
Add
(
_paging
.
Count
);
}
if
(
_filters
!=
null
&&
_filters
.
Count
>
0
)
{
foreach
(
var
f
in
_filters
)
{
f
.
SerializeRedisArgs
(
args
);
}
}
}
/// <summary>
/// Limit the results to a certain offset and limit
/// </summary>
/// <param name="offset">the first result to show, zero based indexing</param>
/// <param name="limit">how many results we want to show</param>
/// <returns>the query itself, for builder-style syntax</returns>
public
Query
Limit
(
int
offset
,
int
count
)
{
_paging
=
new
Paging
(
offset
,
count
);
return
this
;
}
/// <summary>
/// Add a filter to the query's filter list
/// </summary>
/// <param name="f">either a numeric or geo filter object</param>
/// <returns>the query itself</returns>
public
Query
AddFilter
(
Filter
f
)
{
_filters
.
Add
(
f
);
return
this
;
}
/// <summary>
/// Limit the query to results that are limited to a specific set of fields
/// </summary>
/// <param name="fields">a list of TEXT fields in the schemas</param>
/// <returns>the query object itself</returns>
public
Query
LimitFields
(
params
string
[]
fields
)
{
this
.
_fields
=
fields
;
return
this
;
}
}
}
StackExchange.Redis.Modules/RediSearch/Schema.cs
0 → 100644
View file @
eba20f84
// .NET port of https://github.com/RedisLabs/JRediSearch/
using
System
;
using
System.Collections.Generic
;
namespace
StackExchange.Redis.Modules.RediSearch
{
/// <summary>
/// Schema abstracts the schema definition when creating an index.
/// Documents can contain fields not mentioned in the schema, but the index will only index pre-defined fields
/// </summary>
public
sealed
class
Schema
{
public
enum
FieldType
{
FullText
,
Geo
,
Numeric
}
public
class
Field
{
public
String
Name
{
get
;
}
public
FieldType
Type
{
get
;
}
internal
Field
(
string
name
,
FieldType
type
)
{
Name
=
name
;
Type
=
type
;
}
internal
virtual
void
SerializeRedisArgs
(
List
<
object
>
args
)
{
object
GetForRedis
(
FieldType
type
)
{
switch
(
type
)
{
case
FieldType
.
FullText
:
return
"TEXT"
.
Literal
();
case
FieldType
.
Geo
:
return
"GEO"
.
Literal
();
case
FieldType
.
Numeric
:
return
"NUMERIC"
.
Literal
();
default
:
throw
new
ArgumentOutOfRangeException
(
nameof
(
type
));
}
}
args
.
Add
(
Name
);
args
.
Add
(
GetForRedis
(
Type
));
}
}
public
class
TextField
:
Field
{
public
double
Weight
{
get
;
}
internal
TextField
(
string
name
,
double
weight
=
1.0
)
:
base
(
name
,
FieldType
.
FullText
)
{
Weight
=
weight
;
}
internal
override
void
SerializeRedisArgs
(
List
<
object
>
args
)
{
base
.
SerializeRedisArgs
(
args
);
if
(
Weight
!=
1.0
)
{
args
.
Add
(
"WEIGHT"
.
Literal
());
args
.
Add
(
Weight
);
}
}
}
public
List
<
Field
>
Fields
{
get
;
}
=
new
List
<
Field
>();
/// <summary>
/// Add a text field to the schema with a given weight
/// </summary>
/// <param name="name">the field's name</param>
/// <param name="weight">its weight, a positive floating point number</param>
/// <returns>the schema object</returns>
public
Schema
AddTextField
(
string
name
,
double
weight
=
1.0
)
{
Fields
.
Add
(
new
TextField
(
name
,
weight
));
return
this
;
}
/// <summary>
/// Add a numeric field to the schema
/// </summary>
/// <param name="name">the field's name</param>
/// <returns>the schema object</returns>
public
Schema
AddGeoField
(
string
name
)
{
Fields
.
Add
(
new
Field
(
name
,
FieldType
.
Geo
));
return
this
;
}
/// <summary>
/// Add a numeric field to the schema
/// </summary>
/// <param name="name">the field's name</param>
/// <returns>the schema object</returns>
public
Schema
AddNumericField
(
string
name
)
{
Fields
.
Add
(
new
Field
(
name
,
FieldType
.
Numeric
));
return
this
;
}
}
}
StackExchange.Redis.Modules/RediSearch/SearchResult.cs
0 → 100644
View file @
eba20f84
// .NET port of https://github.com/RedisLabs/JRediSearch/
using
StackExchange.Redis
;
using
System.Collections.Generic
;
namespace
StackExchange.Redis.Modules.RediSearch
{
/// <summary>
/// SearchResult encapsulates the returned result from a search query.
/// It contains publically accessible fields for the total number of results, and an array of <see cref="Document"/>
/// objects conatining the actual returned documents.
/// </summary>
public
class
SearchResult
{
public
long
TotalResults
{
get
;
}
public
List
<
Document
>
Documents
{
get
;
}
internal
SearchResult
(
RedisResult
[]
resp
,
bool
hasContent
,
bool
hasScores
,
bool
hasPayloads
)
{
// Calculate the step distance to walk over the results.
// The order of results is id, score (if withScore), payLoad (if hasPayloads), fields
int
step
=
1
;
int
scoreOffset
=
0
;
int
contentOffset
=
1
;
int
payloadOffset
=
0
;
if
(
hasScores
)
{
step
+=
1
;
scoreOffset
=
1
;
contentOffset
+=
1
;
}
if
(
hasContent
)
{
step
+=
1
;
if
(
hasPayloads
)
{
payloadOffset
=
scoreOffset
+
1
;
step
+=
1
;
contentOffset
+=
1
;
}
}
// the first element is always the number of results
TotalResults
=
(
long
)
resp
[
0
];
var
docs
=
new
List
<
Document
>((
resp
.
Length
-
1
)
/
step
);
Documents
=
docs
;
for
(
int
i
=
1
;
i
<
resp
.
Length
;
i
+=
step
)
{
var
id
=
(
string
)
resp
[
i
];
double
score
=
1.0
;
byte
[]
payload
=
null
;
RedisValue
[]
fields
=
null
;
if
(
hasScores
)
{
score
=
(
double
)
resp
[
i
+
scoreOffset
];
}
if
(
hasPayloads
)
{
payload
=
(
byte
[])
resp
[
i
+
payloadOffset
];
}
if
(
hasContent
)
{
fields
=
(
RedisValue
[])
resp
[
i
+
contentOffset
];
}
docs
.
Add
(
Document
.
Load
(
id
,
score
,
payload
,
fields
));
}
}
}
}
StackExchange.Redis.Modules/StackExchange.Redis.Modules.csproj
0 → 100644
View file @
eba20f84
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(LibraryTargetFrameworks)</TargetFrameworks>
<VersionPrefix>0.1</VersionPrefix>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<PackageTags>Redis;Search;Modules;RediSearch</PackageTags>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StackExchange.Redis\StackExchange.Redis.csproj" />
</ItemGroup>
</Project>
\ No newline at end of file
StackExchange.Redis.Modules/Throttling.cs
0 → 100644
View file @
eba20f84
using
System
;
using
System.Linq
;
using
System.Threading.Tasks
;
namespace
StackExchange.Redis.Modules.Throttling
{
public
static
class
ThrottlingExtensions
{
public
static
ThrottleResult
Throttle
(
this
IDatabase
db
,
RedisKey
key
,
int
maxBurst
,
int
maxPerInterval
,
int
intervalSeconds
=
60
,
int
count
=
1
)
{
return
new
ThrottleResult
(
db
.
Execute
(
"CL.THROTTLE"
,
key
,
maxBurst
.
Boxed
(),
maxPerInterval
.
Boxed
(),
intervalSeconds
.
Boxed
(),
count
.
Boxed
()));
}
public
async
static
Task
<
ThrottleResult
>
ThrottleAsync
(
this
IDatabaseAsync
db
,
RedisKey
key
,
int
maxBurst
,
int
maxPerInterval
,
int
intervalSeconds
=
60
,
int
count
=
1
)
{
return
new
ThrottleResult
(
await
db
.
ExecuteAsync
(
"CL.THROTTLE"
,
key
,
maxBurst
.
Boxed
(),
maxPerInterval
.
Boxed
(),
intervalSeconds
.
Boxed
(),
count
.
Boxed
()));
}
static
readonly
object
[]
_boxedInt32
=
Enumerable
.
Range
(-
1
,
128
).
Select
(
i
=>
(
object
)
i
).
ToArray
();
internal
static
object
Boxed
(
this
int
value
)
=>
value
>=
-
1
&&
value
<=
126
?
_boxedInt32
[
value
+
1
]
:
(
object
)
value
;
}
public
struct
ThrottleResult
{
internal
ThrottleResult
(
RedisResult
result
)
{
var
arr
=
(
int
[])
result
;
Permitted
=
arr
[
0
]
==
0
;
TotalLimit
=
arr
[
1
];
RemainingLimit
=
arr
[
2
];
RetryAfterSeconds
=
arr
[
3
];
ResetAfterSeconds
=
arr
[
4
];
}
/// <summary>Whether the action was limited</summary>
public
bool
Permitted
{
get
;}
/// <summary>The total limit of the key (max_burst + 1). This is equivalent to the common `X-RateLimit-Limit` HTTP header.</summary>
public
int
TotalLimit
{
get
;}
/// <summary>The remaining limit of the key. Equivalent to `X-RateLimit-Remaining`.</summary>
public
int
RemainingLimit
{
get
;}
/// <summary>The number of seconds until the user should retry, and always -1 if the action was allowed. Equivalent to `Retry-After`.</summary>
public
int
RetryAfterSeconds
{
get
;}
/// <summary>The number of seconds until the limit will reset to its maximum capacity. Equivalent to `X-RateLimit-Reset`.</summary>
public
int
ResetAfterSeconds
{
get
;}
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment