Skip to content

Commit 8e9d64a

Browse files
authored
feat(sharding): new type=sharding, fixes and upgrades, new tests, distributed inserts (#4537)
1 parent 365a297 commit 8e9d64a

90 files changed

Lines changed: 6928 additions & 351 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/clt_tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ jobs:
8484
"test/clt-tests/sharding/functional",
8585
"test/clt-tests/sharding/replication",
8686
"test/clt-tests/sharding/syntax",
87+
"test/clt-tests/sharding/rollback",
88+
"test/clt-tests/sharding/regression",
8789
"test/clt-tests/test-configuration",
8890
"test/clt-tests/vector-knn",
8991
]

api/libsphinxclient/smoke_ref.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ command_json: 0
323323
command_callpq: 0
324324
command_cluster: 0
325325
command_getfield: 0
326+
command_shard_write: 0
326327
agent_connect: 1
327328
agent_retry: 0
328329
queries: 17
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Creating a Sharded Table
2+
3+
> NOTE: Auto-sharding requires [Manticore Buddy](../../Installation/Manticore_Buddy.md). If auto-sharding doesn't work, make sure Buddy is installed and running.
4+
5+
Manticore allows for the creation of **sharded tables**, a special table type (`type='shard'`) that transparently fans out reads and writes across multiple underlying physical shards. This feature proves invaluable when scaling your data. You can create both local sharded tables on a single server and replicated sharded tables on a multi-node replication cluster to optimize data distribution.
6+
7+
Sharded tables offer several key benefits for high-performance applications:
8+
- They deliver superior write performance by writing data to multiple shards in parallel, utilizing system resources more efficiently. This parallel writing capability enables higher indexing throughput compared to a single large table.
9+
- Sharded tables support replication out of the box, enhancing high availability. You don't need to handle replication manually. Simply set the replication factor when creating the sharded table, and the system manages everything. This built-in replication ensures continuous data accessibility even if some nodes fail.
10+
11+
#### Options
12+
13+
- `shards='N'` — number of physical shards to create. Required. Must be a positive integer, with a maximum of 3000.
14+
- `rf='M'` — replication factor: the number of copies kept for each shard. Required. On a single node it must be `1`; on a replication cluster it must be between 1 and the number of nodes in the cluster.
15+
- `timeout='S'` — how long (in seconds) to wait for shard preparation during creation. Defaults to `30`. Increase it when creating many shards across many nodes.
16+
17+
Values must be quoted (`shards='10'`, not `shards=10`). Option names are case-insensitive.
18+
19+
#### Creating a Local Sharded Table
20+
21+
To create a local sharded table, create a table as you normally would and add `shards='N'` and `rf='1'`. `shards` is the number of physical shards that will be created behind the table. `rf` is the replication factor. On a single server it must be `1`.
22+
23+
Here's an example that creates a table with 10 shards, with data automatically distributed across them:
24+
25+
```sql
26+
CREATE TABLE local_sharded shards='10' rf='1'
27+
```
28+
29+
After this query you get a single sharded table with all its shards already set up. The underlying physical shards live under the `system` database and are hidden from `SHOW TABLES`; you interact with the sharded table by its public name and the system routes operations to the appropriate shards automatically.
30+
31+
#### Creating a Replicated Sharded Table
32+
33+
To protect against server outages, set up a replication cluster across the nodes you want to participate. Throughout this documentation we'll assume the cluster you created is named `c`. Add all desired nodes by following the [replication](../../Creating_a_cluster/Setting_up_replication/Setting_up_replication.md) instructions, then create the table with the desired replication factor.
34+
35+
For example, let's assume you have a 3-node replication cluster and want a table sharded into 10 shards with one copy of each shard on every node. With three nodes participating you set `rf='3'`:
36+
37+
```sql
38+
CREATE TABLE c:cluster_sharded shards='10' rf='3'
39+
```
40+
41+
After that you can work with the table by its plain name on any cluster node — `INSERT`, `SELECT`, `UPDATE`, and `DELETE` do not require the cluster prefix. The cluster prefix (`c:`) is only used for DDL such as `CREATE TABLE` and `DROP TABLE`.
42+
43+
The default timeout to wait for all processes of shard preparation during creation is 30 seconds. Sometimes, when creating many shards on a replication cluster with multiple nodes, it takes a bit longer due to network latency. If needed, you can increase this timeout via the `timeout` option:
44+
45+
```sql
46+
CREATE TABLE c:cluster_sharded shards='10' rf='3' timeout='60'
47+
```
48+
49+
If the timeout is exceeded, the table creation will fail, and you'll need to retry with a longer timeout value.
50+
51+
#### Dropping a Sharded Table
52+
53+
To drop a sharded table, use the standard `DROP TABLE` command. In a clustered environment, specify the cluster name in the table name to properly target the table you want to drop. `IF EXISTS` is supported.
54+
55+
To delete a local sharded table:
56+
57+
```sql
58+
DROP TABLE local_sharded
59+
DROP TABLE IF EXISTS local_sharded
60+
```
61+
62+
To delete a replicated sharded table:
63+
64+
```sql
65+
DROP TABLE c:cluster_sharded
66+
DROP TABLE IF EXISTS c:cluster_sharded
67+
```
68+
69+
#### Inspecting Sharded Tables
70+
71+
`DESC <table>` returns the user-facing field schema of a sharded table (the columns you declared).
72+
73+
`SHOW CREATE TABLE <table>` returns the user-facing definition with `shards='N' rf='M'` — the internal `type='shard'` topology (the per-shard `local=`/`agent=` clauses and md5-named replication cluster) is intentionally hidden. To inspect the resolved internal topology for diagnostics, use `SHOW CREATE TABLE <table> OPTION force=1`.
74+
75+
Two extra commands are provided to inspect the state of the sharding subsystem:
76+
77+
- `SHOW SHARDING STATUS [[<cluster>:]<table>]` — lists per-shard placement and health. Without arguments it lists all sharded tables; with a table name (optionally cluster-prefixed) it filters to that table. Returned columns: `table`, `shard`, `node`, `status` (`active`/`inactive`), `cluster`, `replication_cluster`, `rf`, and `rf_status` (`ok`/`degraded`/`broken`).
78+
79+
```sql
80+
SHOW SHARDING STATUS
81+
SHOW SHARDING STATUS cluster_sharded
82+
SHOW SHARDING STATUS c:cluster_sharded
83+
```
84+
85+
- `SHOW SHARDING MASTER` — shows which node currently runs the sharding master process and whether it's `active` or `inactive`.
86+
87+
```sql
88+
SHOW SHARDING MASTER
89+
```
90+
91+
<!-- proofread -->
92+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Table Sharding: Current Limitations
2+
3+
When working with sharded tables, be aware of the following limitations:
4+
5+
1. **Local and Clustered Sharded Tables Cannot Coexist on the Same Nodes**:
6+
On a set of nodes that participate in a replication cluster, you cannot mix:
7+
- Local sharded tables (created without a cluster prefix: `create table s ... shards='N' rf='1'`)
8+
- Clustered sharded tables (created with a cluster prefix: `create table c:r ... shards='N' rf='M'`)
9+
10+
**Example**: Consider a 2-node cluster where:
11+
- Table `s` was created independently on each node as a local sharded table
12+
- Table `r` is replicated across both nodes via the cluster
13+
14+
In this setup, attempting to create a clustered sharded table (`create table c:r ... shards='N' rf='2'`) on the same nodes will fail.
15+
16+
2. **Cluster Name Consistency**:
17+
- The sharding subsystem binds to a single cluster name on first use; all clustered sharded tables on these nodes must use that same cluster name.
18+
- Once the cluster name is chosen, it applies to all subsequent clustered sharded table creations.
19+
20+
**Example**: If you create your first clustered sharded table with:
21+
```sql
22+
create table c:users ... shards='N' rf='M'
23+
```
24+
All subsequent clustered sharded tables must reuse cluster `c`:
25+
```sql
26+
create table c:orders ... shards='N' rf='M' -- works
27+
create table d:items ... shards='N' rf='M' -- fails
28+
```
29+
30+
3. **No Table Alterations**:
31+
- Once a sharded table is created, its structure cannot be modified with `ALTER TABLE`.
32+
- To change the schema, you must:
33+
1. Create a new sharded table with the desired structure
34+
2. Copy the data to the new table
35+
3. Drop the old table
36+
37+
4. **Shard Count Limit**:
38+
- Maximum of 3,000 shards per sharded table.
39+
- This limit applies regardless of cluster configuration or table size.
40+
- Plan your sharding strategy accordingly to stay within this limit.
41+
42+
5. **`rf` and `shards` Must Be Quoted Integers**:
43+
- Both options require a quoted numeric value (`shards='10'`, `rf='2'`); unquoted, non-numeric, empty, or fractional values are rejected.
44+
- On a standalone (non-clustered) server, `rf` must be `'1'`.
45+
- On a replication cluster, `rf` must be between `1` and the number of nodes in the cluster.
46+
47+
<!-- proofread -->
48+

manual/english/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
* [≫ Creating a distributed table](Creating_a_table/Creating_a_distributed_table/Creating_a_distributed_table.md)
4040
* [• Creating a local distributed table](Creating_a_table/Creating_a_distributed_table/Creating_a_local_distributed_table.md)
4141
* [• Remote tables](Creating_a_table/Creating_a_distributed_table/Remote_tables.md)
42+
* [⪢ Creating a sharded table](Creating_a_table/Creating_a_sharded_table/Creating_a_sharded_table.md)
43+
* [• Table sharding: current limitations](Creating_a_table/Creating_a_sharded_table/Table_sharding_current_limitations.md)
4244
* [• Listing tables](Listing_tables.md)
4345
* [• Deleting a table](Deleting_a_table.md)
4446
* [• Emptying a table](Emptying_a_table.md)

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ set ( SEARCHD_H searchdaemon.h searchdconfig.h searchdddl.h searchdexpr.h search
171171
client_session.h compressed_zstd_mysql.h docs_collector.h index_rotator.h config_reloader.h searchdhttp.h timeout_queue.h
172172
netpoll.h pollable_event.h netfetch.h searchdbuddy.h sphinxql_second.h sphinxql_extra.h sphinxjsonquery.h
173173
frontendschema.h debug_cmds.h dynamic_idx.h sphinxexcerpt.h querystats.h
174+
searchd_shard.h
174175
facetutils.h
175176
)
176177

@@ -214,6 +215,7 @@ add_library ( lsearchd OBJECT searchdha.cpp http/http_parser.c searchdhttp.cpp
214215
sphinxql_debug.cpp sphinxql_second.cpp stackmock.cpp docs_collector.cpp index_rotator.cpp config_reloader.cpp netpoll.cpp
215216
pollable_event.cpp netfetch.cpp searchdbuddy.cpp searchdhttpcompat.cpp sphinxql_extra.cpp searchdreplication.cpp sphinxjsonquery.cpp
216217
frontendschema.cpp compressed_http.cpp debug_cmds.cpp jsonqueryfilter.cpp dynamic_idx.cpp sphinxexcerpt.cpp searchdexpr.cpp querystats.cpp
218+
searchd_shard.cpp
217219
facetutils.cpp
218220
)
219221
target_sources ( lsearchd PUBLIC ${SEARCHD_SRCS_TESTABLE} ${SEARCHD_H} ${SEARCHD_BISON} ${SEARCHD_FLEX} )

src/client_session.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "queryprofile.h"
1818
#include "searchdaemon.h"
1919
#include "searchdsql.h"
20+
#include "searchd_shard.h"
2021
#include "sphinxpq.h"
2122

2223
constexpr const char* szManticore = "Manticore";
@@ -42,6 +43,7 @@ class ClientSession_c final
4243
CSphString m_sError;
4344
CSphQueryResultMeta m_tLastMeta;
4445
CSphSessionAccum m_tAcc;
46+
ShardTxnState_t m_tShardTxn;
4547
CPqResult m_tPercolateMeta;
4648
SqlStmt_e m_eLastStmt { STMT_DUMMY };
4749
bool m_bFederatedUser = false;
@@ -57,6 +59,7 @@ class ClientSession_c final
5759
QueryProfile_c m_tLastProfile;
5860
bool m_bOptimizeById = true;
5961
bool m_bDeprecatedEOF = false;
62+
bool m_bShardPhysicalUpdate = false;
6063
StrVec_t m_dLockedTables;
6164
PreparedStatements m_dPreparedStatements;
6265

src/config_reloader.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class ConfigReloader_c::Impl_c final
102102
void LoadNewLocalFromConfig ( const CSphString& sIndex, const CSphConfigSection& hIndex )
103103
{
104104
CSphString sError;
105-
auto [ eAdd, pFreshLocal ] = AddIndex ( sIndex.cstr(), hIndex, false, false, nullptr, sError );
105+
auto [ eAdd, pFreshLocal ] = AddIndex ( sIndex.cstr(), hIndex, false, false, true, nullptr, sError );
106106
assert ( eAdd != ADD_DISTR && "internal error: distr table should not be here!" );
107107

108108
switch ( eAdd )

src/indexsettings.cpp

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2165,6 +2165,18 @@ static void FormatAllSettings ( const CSphIndex & tIndex, SettingsFormatter_c &
21652165
}
21662166

21672167

2168+
static void FormatAllSettings ( const CSphIndexSettings & tSettings, const CSphFieldFilterSettings & tFieldFilterSettings, const CSphTokenizerSettings & tTokenizerSettings, const CSphDictSettings & tDictSettings, const MutableIndexSettings_c & tMutableSettings, SettingsFormatter_c & tFormatter, FilenameBuilder_i * pFilenameBuilder )
2169+
{
2170+
tSettings.Format ( tFormatter, pFilenameBuilder );
2171+
tFieldFilterSettings.Format ( tFormatter, pFilenameBuilder );
2172+
tTokenizerSettings.Format ( tFormatter, pFilenameBuilder );
2173+
tDictSettings.Format ( tFormatter, pFilenameBuilder );
2174+
tMutableSettings.Format ( tFormatter, pFilenameBuilder );
2175+
if ( !tSettings.m_bBinlog )
2176+
tFormatter.Add ( "binlog", "0", true );
2177+
}
2178+
2179+
21682180
// fixme! this is basically a duplicate of the above function, but has extra code due to embedded
21692181
void DumpReadable ( FILE * fp, const CSphIndex & tIndex, const CSphEmbeddedFiles & tEmbeddedFiles, FilenameBuilder_i * pFilenameBuilder )
21702182
{
@@ -2218,6 +2230,14 @@ static void DumpCreateTable ( StringBuilder_c & tBuf, const CSphIndex & tIndex,
22182230
FormatAllSettings ( tIndex, tFormatter, pFilenameBuilder );
22192231
}
22202232

2233+
2234+
static void DumpCreateTable ( StringBuilder_c & tBuf, const CSphIndexSettings & tSettings, const CSphFieldFilterSettings & tFieldFilterSettings, const CSphTokenizerSettings & tTokenizerSettings, const CSphDictSettings & tDictSettings, const MutableIndexSettings_c & tMutableSettings, FilenameBuilder_i * pFilenameBuilder, ExtFilesFormat_e eExt )
2235+
{
2236+
SettingsFormatterState_t tState(tBuf);
2237+
SettingsFormatter_c tFormatter ( tState, "", "='", "'", " ", false, true, eExt );
2238+
FormatAllSettings ( tSettings, tFieldFilterSettings, tTokenizerSettings, tDictSettings, tMutableSettings, tFormatter, pFilenameBuilder );
2239+
}
2240+
22212241
//////////////////////////////////////////////////////////////////////////
22222242

22232243
static void AddWarning ( StrVec_t & dWarnings, const CSphString & sWarning )
@@ -2367,12 +2387,12 @@ static void AddFieldSettings ( StringBuilder_c & sRes, const CSphColumnInfo & tF
23672387
}
23682388

23692389

2370-
static void AddStorageSettings ( StringBuilder_c & sRes, const CSphColumnInfo & tAttr, const CSphIndex & tIndex, bool bField, int iNumColumnar )
2390+
static void AddStorageSettings ( StringBuilder_c & sRes, const CSphColumnInfo & tAttr, const CSphIndexSettings & tSettings, bool bField, int iNumColumnar )
23712391
{
23722392
if ( !bField && tAttr.m_eAttrType==SPH_ATTR_STRING )
23732393
sRes << " attribute";
23742394

2375-
bool bColumnar = CombineEngines ( tIndex.GetSettings().m_eEngine, tAttr.m_eEngine )==AttrEngine_e::COLUMNAR;
2395+
bool bColumnar = CombineEngines ( tSettings.m_eEngine, tAttr.m_eEngine )==AttrEngine_e::COLUMNAR;
23762396
if ( bColumnar )
23772397
{
23782398
if ( tAttr.m_eAttrType!=SPH_ATTR_JSON && !tAttr.IsStored() && iNumColumnar>1 )
@@ -2461,7 +2481,7 @@ static bool IsDDLToken ( const CSphString & sTok )
24612481
}
24622482

24632483

2464-
static CSphString FormatCreateTableAttr ( const CSphColumnInfo & tAttr, const CSphIndex * pIndex, int iNumColumnar, bool bQuote )
2484+
static CSphString FormatCreateTableAttr ( const CSphColumnInfo & tAttr, const CSphIndexSettings & tSettings, int iNumColumnar, bool bQuote )
24652485
{
24662486
StringBuilder_c sRes;
24672487

@@ -2474,7 +2494,7 @@ static CSphString FormatCreateTableAttr ( const CSphColumnInfo & tAttr, const CS
24742494

24752495
sRes << sQuotedName << " " << GetAttrTypeName(tAttr);
24762496

2477-
AddStorageSettings ( sRes, tAttr, *pIndex, false, iNumColumnar );
2497+
AddStorageSettings ( sRes, tAttr, tSettings, false, iNumColumnar );
24782498
AddEngineSettings ( sRes, tAttr );
24792499
AddKNNSettings ( sRes, tAttr );
24802500
AddSISettings ( sRes, tAttr );
@@ -2483,7 +2503,7 @@ static CSphString FormatCreateTableAttr ( const CSphColumnInfo & tAttr, const CS
24832503
}
24842504

24852505

2486-
static CSphString FormatCreateTableField ( const CSphColumnInfo & tField, const CSphIndex * pIndex, const CSphSchema & tSchema, int iNumColumnar, bool bQuote )
2506+
static CSphString FormatCreateTableField ( const CSphColumnInfo & tField, const CSphIndexSettings & tSettings, const CSphSchema & tSchema, int iNumColumnar, bool bQuote )
24872507
{
24882508
StringBuilder_c sRes;
24892509

@@ -2504,25 +2524,21 @@ static CSphString FormatCreateTableField ( const CSphColumnInfo & tField, const
25042524
{
25052525
sRes << " attribute";
25062526

2507-
AddStorageSettings ( sRes, *pAttr, *pIndex, true, iNumColumnar );
2527+
AddStorageSettings ( sRes, *pAttr, tSettings, true, iNumColumnar );
25082528
AddEngineSettings ( sRes, *pAttr );
25092529
}
25102530

25112531
return sRes.cstr();
25122532
}
25132533

25142534

2515-
CSphString BuildCreateTable ( const CSphString & sName, const CSphIndex * pIndex, const CSphSchema & tSchema, ExtFilesFormat_e eExt )
2535+
template <typename DUMP_SETTINGS>
2536+
static CSphString BuildCreateTableImpl ( const CSphString & sName, const CSphSchema & tSchema, const CSphIndexSettings & tSettings, DUMP_SETTINGS && fnDumpSettings )
25162537
{
2517-
assert ( pIndex );
2518-
25192538
auto& tSess = session::Info();
25202539
bool bQuote = tSess.GetSqlQuoteShowCreate();
25212540

2522-
int iNumColumnar = 0;
2523-
for ( int i = 0; i < tSchema.GetAttrsCount(); i++ )
2524-
if ( tSchema.GetAttr(i).IsColumnar() )
2525-
iNumColumnar++;
2541+
int iNumColumnar = tSchema.GetColumnarAttrsCount();
25262542

25272543
StringBuilder_c sRes;
25282544
sRes << "CREATE TABLE " << ( bQuote ? SphSprintf ( "`%s`", sName.cstr() ) : sName) << " (\n";
@@ -2541,12 +2557,12 @@ CSphString BuildCreateTable ( const CSphString & sName, const CSphIndex * pIndex
25412557
const CSphColumnInfo * pId = tSchema.GetAttr("id");
25422558
assert(pId);
25432559

2544-
sRes << FormatCreateTableAttr ( *pId, pIndex, iNumColumnar, bQuote );
2560+
sRes << FormatCreateTableAttr ( *pId, tSettings, iNumColumnar, bQuote );
25452561

25462562
for ( int i = 0; i < tSchema.GetFieldsCount(); i++ )
25472563
{
25482564
sRes << ",\n";
2549-
sRes << FormatCreateTableField ( tSchema.GetField(i), pIndex, tSchema, iNumColumnar, bQuote );
2565+
sRes << FormatCreateTableField ( tSchema.GetField(i), tSettings, tSchema, iNumColumnar, bQuote );
25502566
}
25512567

25522568
for ( int i = 0; i < tSchema.GetAttrsCount(); i++ )
@@ -2559,24 +2575,37 @@ CSphString BuildCreateTable ( const CSphString & sName, const CSphIndex * pIndex
25592575
continue;
25602576

25612577
sRes << ",\n";
2562-
sRes << FormatCreateTableAttr ( tAttr, pIndex, iNumColumnar, bQuote );
2578+
sRes << FormatCreateTableAttr ( tAttr, tSettings, iNumColumnar, bQuote );
25632579
}
25642580

25652581
sRes << "\n)";
25662582

25672583
StringBuilder_c tBuf;
2584+
fnDumpSettings ( tBuf );
2585+
2586+
if ( tBuf.GetLength() )
2587+
sRes << " " << tBuf.cstr();
2588+
2589+
CSphString sResult = sRes.cstr();
2590+
return sResult;
2591+
}
2592+
2593+
2594+
CSphString BuildCreateTable ( const CSphString & sName, const CSphIndex * pIndex, const CSphSchema & tSchema, ExtFilesFormat_e eExt )
2595+
{
2596+
assert ( pIndex );
25682597

25692598
std::unique_ptr<FilenameBuilder_i> pFilenameBuilder;
25702599
if ( g_fnCreateFilenameBuilder )
25712600
pFilenameBuilder = g_fnCreateFilenameBuilder ( pIndex->GetName() );
25722601

2573-
DumpCreateTable ( tBuf, *pIndex, pFilenameBuilder.get(), eExt );
2602+
return BuildCreateTableImpl ( sName, tSchema, pIndex->GetSettings(), [&] ( StringBuilder_c & tBuf ) { DumpCreateTable ( tBuf, *pIndex, pFilenameBuilder.get(), eExt ); } );
2603+
}
25742604

2575-
if ( tBuf.GetLength() )
2576-
sRes << " " << tBuf.cstr();
25772605

2578-
CSphString sResult = sRes.cstr();
2579-
return sResult;
2606+
CSphString BuildCreateTable ( const CSphString & sName, const CSphSchema & tSchema, const CSphIndexSettings & tSettings, const CSphFieldFilterSettings & tFieldFilterSettings, const CSphTokenizerSettings & tTokenizerSettings, const CSphDictSettings & tDictSettings, const MutableIndexSettings_c & tMutableSettings, ExtFilesFormat_e eExt, FilenameBuilder_i * pFilenameBuilder )
2607+
{
2608+
return BuildCreateTableImpl ( sName, tSchema, tSettings, [&] ( StringBuilder_c & tBuf ) { DumpCreateTable ( tBuf, tSettings, tFieldFilterSettings, tTokenizerSettings, tDictSettings, tMutableSettings, pFilenameBuilder, eExt ); } );
25802609
}
25812610

25822611

0 commit comments

Comments
 (0)