Skip to content

Latest commit

 

History

History
671 lines (512 loc) · 15.3 KB

File metadata and controls

671 lines (512 loc) · 15.3 KB

Collation Support Guide - Complete Reference

Version: 1.2.0
Status: ✅ Complete (Phases 1-7)
Last Updated: January 28, 2025


Table of Contents

  1. Overview
  2. What is Collation?
  3. Supported Collation Types
  4. Schema Design with Collations
  5. Query Examples
  6. Migration & Compatibility
  7. Performance Implications
  8. Best Practices

Overview

SharpCoreDB supports 4 collation types across 7 implementation phases:

Collation Phase Query Context Performance Use Case
BINARY Phase 1-3 WHERE, ORDER BY, GROUP BY Baseline Default, case-sensitive
NOCASE Phase 3-7 WHERE, ORDER BY, JOINs +5% Case-insensitive searches
RTRIM Phase 5-7 WHERE, JOINs +3% Trailing space comparison
UNICODE Phase 6-7 WHERE, JOINs +8% Accent handling

What is Collation?

Collation defines how strings are compared and sorted.

Example: Why It Matters

-- Without collation (Binary/case-sensitive):
SELECT * FROM users WHERE name = 'alice';
-- Returns: 0 rows (only matches "alice" exactly)

-- With NOCASE collation:
SELECT * FROM users WHERE name = 'alice';
-- Returns: 1 row (matches "alice", "Alice", "ALICE", etc.)

Three Collation Contexts

  1. Schema Level: Default for the column
  2. Query Level: Override in WHERE clause
  3. Index Level: How the index compares keys

Supported Collation Types

1. BINARY (Default)

Behavior: Exact byte-by-byte comparison (case-sensitive, accent-sensitive)

DDL:

CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    name TEXT COLLATE BINARY,  -- Explicit (optional - this is default)
    email TEXT                  -- Implicit BINARY
);

Comparison Results:

'alice' = 'alice'   → TRUE
'alice' = 'Alice'   → FALSE (case matters)
'café' = 'cafe'     → FALSE (accent matters)

Sorting Order:

Numbers: 0-9
Uppercase: A-Z
Lowercase: a-z
Special: Etc.

Performance:Baseline - No overhead

Best For:

  • System identifiers (IDs, codes)
  • Password comparisons
  • Case-sensitive searches
  • Full precision required

2. NOCASE

Behavior: Case-insensitive, accent-aware comparison

DDL:

CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    username TEXT COLLATE NOCASE,  -- Case-insensitive
    email TEXT COLLATE NOCASE      -- Same
);

Comparison Results:

'alice' = 'alice'   → TRUE
'alice' = 'Alice'   → TRUE (case ignored)
'alice' = 'ALICE'   → TRUE (case ignored)
'café' = 'cafe'     → FALSE (accent still matters)

Sorting Order:

A/a, B/b, C/c, Ä/ä, D/d
-- All case variants grouped together, accent-sensitive

Performance:+5% overhead vs BINARY (ToLower conversion)

Query Examples:

-- Case-insensitive search
SELECT * FROM users WHERE username = 'Alice';
-- Matches: 'alice', 'Alice', 'ALICE', 'aLiCe'

-- Case-insensitive filtering
SELECT * FROM products WHERE category LIKE '%ELECTRONICS%';
-- Matches: 'Electronics', 'electronics', 'ELECTRONICS'

-- Case-insensitive sorting
SELECT * FROM users ORDER BY username;
-- Groups: Alice, alice, ALICE (all together)

Best For:

  • User authentication (usernames)
  • Product names (case doesn't matter)
  • Email addresses (user part often case-insensitive)
  • General text searches

3. RTRIM

Behavior: Trailing whitespace ignored, case-sensitive

DDL:

CREATE TABLE entries (
    id INTEGER PRIMARY KEY,
    code TEXT COLLATE RTRIM  -- Trailing spaces ignored
);

Comparison Results:

'hello' = 'hello'       → TRUE
'hello' = 'hello   '    → TRUE (trailing spaces ignored)
'hello' = 'Hello'       → FALSE (case still matters)
'hello' = ' hello'      → FALSE (leading spaces NOT ignored)

Sorting Order:

'abc', 'abc   ', 'abcd'
-- Treats 'abc' and 'abc   ' as equal

Performance:+3% overhead (whitespace trimming)

Use Cases:

-- Legacy data with inconsistent spacing
SELECT * FROM legacy_codes WHERE code = 'ABC123' COLLATE RTRIM;

-- Fixed-width field comparison
SELECT * FROM padded_ids WHERE id_field = 'ID001   ' COLLATE RTRIM;

Best For:

  • Legacy database migration
  • Fixed-width field comparison
  • Data cleaning operations

4. UNICODE

Behavior: Accent-insensitive, case-insensitive, international support

DDL:

CREATE TABLE international (
    id INTEGER PRIMARY KEY,
    name TEXT COLLATE UNICODE  -- Full Unicode normalization
);

Comparison Results:

'cafe' = 'café'      → TRUE (accent ignored)
'cafe' = 'CAFÉ'      → TRUE (case and accent ignored)
'naïve' = 'naive'    → TRUE (diaeresis ignored)
'Å' = 'å'            → TRUE (both case and form)

Sorting Order:

Without accents: a, b, c, d, e
With accents: á, à, ä, â all group with 'a'

Performance: ⚠️ +8% overhead (Unicode normalization)

Query Examples:

-- International name matching
SELECT * FROM contacts WHERE name = 'José';
-- Matches: 'jose', 'José', 'JOSÉ', 'Jose'

-- Diacritic-insensitive search
SELECT * FROM products WHERE description LIKE '%cafe%' COLLATE UNICODE;
-- Matches: 'cafe', 'café', 'Café', 'CAFÉ'

-- Multi-language sorting
SELECT * FROM directory ORDER BY name COLLATE UNICODE;
-- Proper ordering for: José, João, Jürgen, Jean

Best For:

  • International applications
  • Multilingual databases
  • User-facing searches
  • Global contact systems

Schema Design with Collations

Single-Column Collation

CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    username TEXT COLLATE NOCASE,       -- Case-insensitive username
    email TEXT COLLATE NOCASE,          -- Case-insensitive email
    display_name TEXT COLLATE UNICODE,  -- International name
    notes TEXT                          -- Default BINARY
);

Multi-Column Collation Strategy

CREATE TABLE products (
    sku TEXT PRIMARY KEY COLLATE BINARY,        -- Must be exact
    name TEXT COLLATE UNICODE,                  -- International support
    category TEXT COLLATE NOCASE,               -- User-friendly
    internal_code TEXT COLLATE RTRIM,           -- Legacy system code
    description TEXT COLLATE NOCASE             -- Searchable content
);

Index Collation

CREATE TABLE articles (
    id INTEGER PRIMARY KEY,
    title TEXT COLLATE NOCASE,
    content TEXT
);

-- Index respects column collation
CREATE INDEX idx_title ON articles(title);
-- This index is NOCASE-aware

-- Or specify explicit collation in index
CREATE INDEX idx_title_binary ON articles(title COLLATE BINARY);

Query Examples

WHERE Clause with Collation

-- Implicit: uses column collation
SELECT * FROM users WHERE username = 'Alice';

-- Explicit: override column collation
SELECT * FROM users WHERE username = 'Alice' COLLATE BINARY;

-- Filter with collation
SELECT * FROM users WHERE email LIKE '%@GMAIL.COM%' COLLATE NOCASE;

ORDER BY with Collation

-- Sort case-insensitively
SELECT * FROM users ORDER BY username COLLATE NOCASE;

-- Sort with accents grouped
SELECT * FROM products ORDER BY name COLLATE UNICODE;

-- Reverse case-insensitive sort
SELECT * FROM categories ORDER BY name COLLATE NOCASE DESC;

GROUP BY with Collation

-- Group case-insensitively
SELECT username, COUNT(*) 
FROM login_attempts 
GROUP BY username COLLATE NOCASE;
-- Result: Groups 'alice', 'Alice', 'ALICE' together

-- Group by international names
SELECT name, COUNT(*)
FROM customers
GROUP BY name COLLATE UNICODE;

JOINs with Collation

-- JOIN with collation mismatch warning
SELECT u.name, o.user_name
FROM users u
JOIN orders o ON u.name = o.user_name;
-- Warning: Column collations differ (BINARY vs NOCASE)

-- Explicit collation to match
SELECT u.name, o.user_name
FROM users u
JOIN orders o ON u.name COLLATE NOCASE = o.user_name COLLATE NOCASE;

DISTINCT with Collation

-- Case-insensitive distinct
SELECT DISTINCT username COLLATE NOCASE
FROM user_history;
-- Returns: 1 row for 'alice', 'Alice', 'ALICE'

-- Case-sensitive distinct (default)
SELECT DISTINCT username
FROM user_history;
-- Returns: 3 rows for 'alice', 'Alice', 'ALICE'

Migration & Compatibility

Adding Collation to Existing Column

-- Before: CREATE TABLE users (name TEXT);
-- After: Add collation

ALTER TABLE users MODIFY COLUMN name TEXT COLLATE NOCASE;

-- Validate no data conflicts
SELECT * FROM users WHERE name = name COLLATE BINARY;
-- Should return 0 rows if no duplicates exist

-- Rebuild affected indexes
DROP INDEX idx_name;
CREATE INDEX idx_name ON users(name);

Changing Collation Type

-- From NOCASE to UNICODE (adds accent handling)
ALTER TABLE products MODIFY COLUMN name TEXT COLLATE UNICODE;

-- Validation query - find potential conflicts
SELECT name, COUNT(*) as cnt
FROM products
GROUP BY name COLLATE UNICODE
HAVING COUNT(*) > 1;
-- If any rows returned, there are collation conflicts

EF Core Integration

using Microsoft.EntityFrameworkCore;

public class User
{
    public int Id { get; set; }
    
    [Unicode(false)]  // Case-sensitive
    public string Email { get; set; }
    
    [Unicode(true)]   // Case-insensitive
    public string Username { get; set; }
}

public class DbContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>()
            .Property(u => u.Email)
            .UseCollation("NOCASE");
            
        modelBuilder.Entity<User>()
            .Property(u => u.Username)
            .UseCollation("UNICODE");
    }
}

Performance Implications

Overhead by Collation Type

BINARY:    0%     (baseline)
RTRIM:     +3%    (whitespace trimming)
NOCASE:    +5%    (ToLower conversion)
UNICODE:   +8%    (normalization)

Query Performance Test

// Test different collations
var stopwatch = Stopwatch.StartNew();

for (int i = 0; i < 100000; i++)
{
    var result = await db.ExecuteQueryAsync(
        "SELECT * FROM users WHERE username = @name",
        new[] { ("@name", (object)$"user{i}") }
    );
}

stopwatch.Stop();
Console.WriteLine($"Time: {stopwatch.ElapsedMilliseconds}ms");

// Expected:
// BINARY: 500ms
// NOCASE: 525ms  (+5%)
// UNICODE: 540ms (+8%)

Index Overhead

Index Size Comparison (1M strings):

BINARY:  50MB
NOCASE:  50MB    (same - collation doesn't change storage)
UNICODE: 50MB    (same)

Query Speed Comparison:

BINARY search:  1.0ms
NOCASE search:  1.05ms  (+5% - collation comparison overhead)
UNICODE search: 1.08ms  (+8%)

Recommendations

Workload Recommended Reason
High-performance reads BINARY No overhead
User authentication NOCASE Minimal overhead, essential feature
Reporting/Analytics BINARY Large aggregations benefit from speed
International app UNICODE Small overhead worth the feature
Mixed workload NOCASE Good balance

Best Practices

1. Consistency

Do:

-- All user identifiers same collation
CREATE TABLE users (
    username TEXT COLLATE NOCASE,
    email TEXT COLLATE NOCASE,
    created_at DATETIME
);

Don't:

-- Inconsistent collations
CREATE TABLE users (
    username TEXT COLLATE NOCASE,
    email TEXT COLLATE BINARY  -- Different! Will cause issues in JOINs
);

2. Document Your Choice

-- Add comments explaining collation choice
CREATE TABLE products (
    -- Case-sensitive SKU for inventory system
    sku TEXT PRIMARY KEY COLLATE BINARY,
    
    -- Case-insensitive for user-friendly search
    name TEXT COLLATE NOCASE,
    
    -- International product names
    description TEXT COLLATE UNICODE
);

3. Test Edge Cases

// Before deploying, test with real data
var testCases = new[]
{
    ("Alice", "alice"),      // Case variation
    ("Café", "cafe"),        // Accents
    ("hello ", "hello"),     // Whitespace
    ("日本", "日本"),         // Unicode
};

foreach (var (test1, test2) in testCases)
{
    var result = await db.ExecuteQueryAsync(
        $"SELECT @v1 = @v2 COLLATE NOCASE",
        new[] { ("@v1", (object)test1), ("@v2", (object)test2) }
    );
    
    Console.WriteLine($"{test1} = {test2}: {result}");
}

4. Plan for Growth

-- If you might need international support later, use UNICODE now
CREATE TABLE future_proof (
    name TEXT COLLATE UNICODE,  -- Future-proof
    created_at DATETIME
);

-- Changing from BINARY → UNICODE later is expensive
-- Better to use UNICODE from the start

5. Monitor Performance

// Create performance baselines
public class CollationBenchmark
{
    [Benchmark]
    public void BinaryCollationSearch() { /* test BINARY */ }
    
    [Benchmark]
    public void NoCaseCollationSearch() { /* test NOCASE */ }
    
    [Benchmark]
    public void UnicodeCollationSearch() { /* test UNICODE */ }
}

// Run: dotnet run -c Release --filter "*Collation*"

Implementation Phases (Reference)

Phase Feature Status
Phase 1 COLLATE syntax in DDL ✅ Complete
Phase 2 Parser integration & storage ✅ Complete
Phase 3 WHERE clause support ✅ Complete
Phase 4 ORDER BY, GROUP BY, DISTINCT ✅ Complete
Phase 5 Runtime optimization & LIKE ✅ Complete
Phase 6 ALTER TABLE & schema migration ✅ Complete
Phase 7 JOIN collations ✅ Complete

Troubleshooting

Issue: "Collation Mismatch in JOIN"

SELECT * FROM users u
JOIN orders o ON u.name = o.customer_name;
-- Error: Column 'u.name' (NOCASE) vs 'o.customer_name' (BINARY)

Solution:

-- Explicitly match collations
SELECT * FROM users u
JOIN orders o ON u.name COLLATE NOCASE = o.customer_name COLLATE NOCASE;

-- Or standardize schema
ALTER TABLE orders MODIFY COLUMN customer_name TEXT COLLATE NOCASE;

Issue: Performance Degradation After Adding Collation

// After changing to UNICODE, queries are slow

Solution:

-- Rebuild indexes
DROP INDEX idx_name;
CREATE INDEX idx_name ON products(name);

-- Verify index usage
EXPLAIN QUERY PLAN
SELECT * FROM products WHERE name = 'test';

Issue: Unexpected Matching Behavior

-- Expected to NOT match, but did
SELECT 'Café' = 'cafe' COLLATE NOCASE;  -- Returns TRUE

Solution:

-- Specify expected collation explicitly
SELECT 'Café' = 'cafe' COLLATE BINARY;   -- FALSE
SELECT 'Café' = 'cafe' COLLATE NOCASE;   -- FALSE (accent still matters)
SELECT 'Café' = 'cafe' COLLATE UNICODE;  -- TRUE (full normalization)

Quick Reference Table

Need Collation Code Performance
Case-insensitive username NOCASE TEXT COLLATE NOCASE +5%
International names UNICODE TEXT COLLATE UNICODE +8%
Exact matching (default) BINARY TEXT or TEXT COLLATE BINARY Baseline
Legacy fixed-width RTRIM TEXT COLLATE RTRIM +3%

See Also


Status: ✅ Complete
Last Updated: January 28, 2025
Version: 1.2.0