# How Fractional Indexes Control Ordering of Rules and Proxy Groups in FlClash

> Learn how FlClash fractional indexes efficiently order rules and proxy groups using sortable strings, avoiding database-wide renumbering. Discover seamless management in FlClash.

- Repository: [chen08209/FlClash](https://github.com/chen08209/FlClash)
- Tags: internals
- Published: 2026-05-31

---

**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:

```dart
// 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 + 1` operations 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 `generateKeyBetween` can 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:

```dart
// 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:

```dart
// 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.dart` file implements `generateKeyBetween` and `generateNKeys` using base-62 encoding for lexicographic sorting.
- Rules use the `profile_rule_links.order` column, while proxy groups use `proxy_groups.order`.
- Bulk operations like `resetOrders` regenerate the entire keyspace efficiently, while single insertions use `generateKeyBetween` to 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.