How Fractional Indexes Control Ordering of Rules and Proxy Groups in FlClash
FlClash uses fractional indexes—lexicographically sortable strings stored in SQLite order columns—to enable efficient insertion and reordering of rules and proxy groups without database-wide renumbering.
FlClash is a Flutter-based GUI for Clash that manages complex proxy configurations through a local SQLite database. To maintain the visual sequence of rules and proxy groups while supporting drag-and-drop reordering and bulk imports, the application implements a fractional indexing algorithm that assigns each item a sortable string key rather than a dense integer position.
What Are Fractional Indexes in FlClash?
Fractional indexes are variable-length string keys that can be generated to fall lexicographically between any two existing keys. In FlClash, this mechanism lives in lib/common/indexing.dart and provides two core functions:
generateKeyBetween: Creates a new key that sorts between a predecessor and successor key.generateNKeys: Generates a evenly spaced set of keys for bulk operations.
Each key consists of an integer-part (a head character indicating length) followed by a fractional part composed of base-62 digits. Because SQLite compares these keys as plain strings, lexicographic order directly corresponds to the desired visual order in the UI.
Database Schema and Storage Locations
FlClash stores fractional index keys in dedicated order columns within its SQLite database, enabling simple ORDER BY clauses to retrieve items in the correct sequence.
Rules Table
The profile_rule_links table contains an order column that determines the processing sequence of rules. The RulesDao._getSelectStatement method explicitly orders results by profileRuleLinks.order (see lib/database/rules.dart lines 45-48).
Proxy Groups Table
Similarly, the proxy_groups table uses an order column to maintain group hierarchy. The ProxyGroupsDao.query method sorts by this column to ensure consistent UI presentation (see lib/database/groups.dart lines 70-76).
Key Generation Patterns in Practice
FlClash employs different indexing strategies depending on whether it is performing a bulk reset or handling individual insertions.
Bulk Reset Operations
When importing configurations or migrating data, FlClash avoids expensive per-row updates by regenerating the entire keyspace. The RulesDao.resetOrders method reads all rule links, calls indexing.generateNKeys(links.length) to create a fresh set of evenly spaced keys, and writes them back in a single batch transaction (see lib/database/rules.dart lines 93-106).
For proxy groups, ProxyGroupsDao.setAllWithBatch invokes indexing.generateNKeys(proxyGroups.length) before insertion to assign each group a stable initial order key (see lib/database/groups.dart lines 20-26).
Single Insertions and Reordering
When a user drags a rule or proxy group to a new position, FlClash generates a key between the neighboring items:
// Moving a rule between two existing positions
final String? newKey = indexing.generateKeyBetween(oldPrevKey, oldNextKey);
await rulesDao.order(profileId, ruleId: movedRule.id, order: newKey);
This approach eliminates the need for cascading updates to surrounding rows.
Why FlClash Uses Fractional Indexing
The architectural choice to use fractional indexes provides three critical advantages:
- Efficient Inserts: Adding a rule or group requires only a single key calculation. No massive
UPDATE … SET order = order + 1operations are necessary, preventing database locks and write amplification. - Stable UI Ordering: Because keys are stored literally in the database, the UI layer can rely on simple SQL sorting without maintaining complex in-memory state or handling version conflicts.
- Scalable Reordering: After hundreds of drag-and-drop operations, the system remains efficient because
generateKeyBetweencan always find an intermediate lexicographic value, only rarely requiring a full keyspace reset.
Code Implementation Examples
The following patterns demonstrate how FlClash interacts with the indexing system in production code:
// Bulk-reset all rule orders after configuration import
await rulesDao.resetOrders(); // Internally uses generateNKeys
// Insert a new proxy group between existing items
final String orderKey = indexing.generateKeyBetween(
previousGroup.order,
nextGroup.order
);
await proxyGroupsDao.order(
profileId,
proxyGroup: newGroup,
order: orderKey,
);
The indexing.dart file also exposes generateNKeys for scenarios requiring pre-calculation of multiple slots:
// Pre-generate keys for 50 new rules without touching the database
final List<String> newKeys = indexing.generateNKeys(50);
Summary
- FlClash stores ordering information in SQLite using fractional index strings rather than sequential integers.
- The
lib/common/indexing.dartfile implementsgenerateKeyBetweenandgenerateNKeysusing base-62 encoding for lexicographic sorting. - Rules use the
profile_rule_links.ordercolumn, while proxy groups useproxy_groups.order. - Bulk operations like
resetOrdersregenerate the entire keyspace efficiently, while single insertions usegenerateKeyBetweento avoid renumbering. - This architecture supports conflict-free drag-and-drop reordering and stable backup synchronization.
Frequently Asked Questions
How does FlClash handle fractional index collisions?
FlClash mitigates collision risks by using base-62 encoding with variable-length fractional parts. The generateKeyBetween algorithm in lib/common/indexing.dart automatically increases key length when the space between two existing keys becomes too small, ensuring there is always a valid intermediate value available.
What happens when fractional indexes run out of space between two items?
When the lexicographic gap between two keys becomes too narrow (e.g., after many incremental insertions at the same position), the generateKeyBetween function extends the key length by appending additional base-62 digits. If the gap becomes critically small during a drag-and-drop operation, the application may trigger a background reordering task similar to resetOrders to normalize key spacing.
Why not use simple integer columns for ordering?
Dense integer ordering requires updating every row between the insertion point and the end of the list (e.g., UPDATE rules SET order = order + 1 WHERE order >= ?). In FlClash's SQLite implementation, this creates write contention and risks constraint violations during concurrent modifications. Fractional indexes reduce any reordering operation to a single-row update regardless of list position.
Are fractional indexes preserved when exporting or backing up configurations?
Yes. Because the order column values are stored as plain strings in the SQLite database, they persist through standard backup and export operations. When FlClash restores a configuration, the fractional keys maintain their relative sequencing without recalculation, ensuring that rule priorities and proxy group hierarchies remain intact across devices.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →