@@ -2610,6 +2610,199 @@ export TURSO_AUTH_TOKEN="eyJ..."
26102610- 🌍 ** Global distribution** via Turso edge
26112611- 💪 ** Offline capability** - works without network
26122612
2613+ ### Type Encoding and Parameter Conversion
2614+
2615+ EctoLibSql automatically converts Elixir types to SQLite-compatible formats. Understanding these conversions is important for correct database usage.
2616+
2617+ #### Automatically Encoded Types
2618+
2619+ The following types are automatically converted when passed as query parameters:
2620+
2621+ ##### Temporal Types
2622+
2623+ ``` elixir
2624+ # DateTime → ISO8601 string
2625+ dt = DateTime .utc_now ()
2626+ SQL .query! (Repo , " INSERT INTO events (created_at) VALUES (?)" , [dt])
2627+ # Stored as: "2026-01-13T03:45:23.123456Z"
2628+
2629+ # NaiveDateTime → ISO8601 string
2630+ dt = NaiveDateTime .utc_now ()
2631+ SQL .query! (Repo , " INSERT INTO events (created_at) VALUES (?)" , [dt])
2632+ # Stored as: "2026-01-13T03:45:23.123456"
2633+
2634+ # Date → ISO8601 string
2635+ date = Date .utc_today ()
2636+ SQL .query! (Repo , " INSERT INTO events (event_date) VALUES (?)" , [date])
2637+ # Stored as: "2026-01-13"
2638+
2639+ # Time → ISO8601 string
2640+ time = Time .new! (14 , 30 , 45 )
2641+ SQL .query! (Repo , " INSERT INTO events (event_time) VALUES (?)" , [time])
2642+ # Stored as: "14:30:45.000000"
2643+
2644+ # Relative dates (compute absolute date first, then pass)
2645+ tomorrow = Date .add (Date .utc_today (), 1 ) # Becomes a Date struct
2646+ SQL .query! (Repo , " INSERT INTO events (event_date) VALUES (?)" , [tomorrow])
2647+
2648+ # Third-party date types (Timex, etc.) - pre-convert to standard types
2649+ # ❌ NOT SUPPORTED: Timex.DateTime or custom structs
2650+ # ✅ DO THIS: Convert to native DateTime first
2651+ timex_dt = Timex .now ()
2652+ native_dt = Timex .to_datetime (timex_dt) # Convert to DateTime
2653+ SQL .query! (Repo , " INSERT INTO events (created_at) VALUES (?)" , [native_dt])
2654+ ```
2655+
2656+ ##### Boolean Values
2657+
2658+ ``` elixir
2659+ # true → 1, false → 0
2660+ # SQLite uses integers for booleans
2661+ SQL .query! (Repo , " INSERT INTO users (active) VALUES (?)" , [true ])
2662+ # Stored as: 1
2663+
2664+ SQL .query! (Repo , " INSERT INTO users (active) VALUES (?)" , [false ])
2665+ # Stored as: 0
2666+
2667+ # Works with WHERE clauses
2668+ SQL .query! (Repo , " SELECT * FROM users WHERE active = ?" , [true ])
2669+ # Matches rows where active = 1
2670+ ```
2671+
2672+ ##### Decimal Values
2673+
2674+ ``` elixir
2675+ # Decimal → string representation
2676+ decimal = Decimal .new (" 123.45" )
2677+ SQL .query! (Repo , " INSERT INTO prices (amount) VALUES (?)" , [decimal])
2678+ # Stored as: "123.45"
2679+ ```
2680+
2681+ ##### NULL/nil Values
2682+
2683+ ``` elixir
2684+ # nil → NULL
2685+ SQL .query! (Repo , " INSERT INTO users (bio) VALUES (?)" , [nil ])
2686+ # Stored as SQL NULL
2687+
2688+ # :null atom → nil → NULL (v0.8.3+)
2689+ # Alternative way to represent NULL
2690+ SQL .query! (Repo , " INSERT INTO users (bio) VALUES (?)" , [:null ])
2691+ # Also stored as SQL NULL
2692+
2693+ # Both work identically:
2694+ SQL .query! (Repo , " SELECT * FROM users WHERE bio IS NULL" ) # Matches both
2695+ ```
2696+
2697+ ##### UUID Values
2698+
2699+ ``` elixir
2700+ # Ecto.UUID strings work directly (already binary strings)
2701+ uuid = Ecto .UUID .generate ()
2702+ SQL .query! (Repo , " INSERT INTO users (id) VALUES (?)" , [uuid])
2703+ # Stored as: "550e8400-e29b-41d4-a716-446655440000"
2704+
2705+ # Works with WHERE clauses
2706+ SQL .query! (Repo , " SELECT * FROM users WHERE id = ?" , [uuid])
2707+ ```
2708+
2709+ #### Type Encoding Examples
2710+
2711+ ``` elixir
2712+ defmodule MyApp .Examples do
2713+ def example_with_multiple_types do
2714+ import Ecto .Adapters .SQL
2715+
2716+ now = DateTime .utc_now ()
2717+ user_active = true
2718+ amount = Decimal .new (" 99.99" )
2719+
2720+ # All types are automatically encoded
2721+ query! (Repo ,
2722+ " INSERT INTO transactions (created_at, active, amount) VALUES (?, ?, ?)" ,
2723+ [now, user_active, amount]
2724+ )
2725+ end
2726+
2727+ def example_with_ecto_queries do
2728+ import Ecto .Query
2729+
2730+ from (u in User ,
2731+ where: u.active == ^true , # Boolean encoded to 1
2732+ where: u.created_at > ^DateTime .utc_now () # DateTime encoded to ISO8601
2733+ )
2734+ |> Repo .all ()
2735+ end
2736+
2737+ def example_with_null do
2738+ # Both are equivalent:
2739+ SQL .query! (Repo , " INSERT INTO users (bio) VALUES (?)" , [nil ])
2740+ SQL .query! (Repo , " INSERT INTO users (bio) VALUES (?)" , [:null ])
2741+
2742+ # Query for NULL values
2743+ SQL .query! (Repo , " SELECT * FROM users WHERE bio IS NULL" )
2744+ end
2745+ end
2746+ ```
2747+
2748+ #### Limitations: Nested Structures with Temporal Types
2749+
2750+ Nested structures (maps/lists) containing temporal types are ** not automatically encoded** . Only top-level parameters are encoded.
2751+
2752+ ``` elixir
2753+ # ❌ DOESN'T WORK - Nested DateTime not encoded
2754+ nested = %{
2755+ " created_at" => DateTime .utc_now (), # ← Not auto-encoded
2756+ " data" => " value"
2757+ }
2758+ SQL .query! (Repo , " INSERT INTO events (metadata) VALUES (?)" , [nested])
2759+ # Error: DateTime struct cannot be serialized to JSON
2760+
2761+ # ✅ WORKS - Pre-encode nested values
2762+ nested = %{
2763+ " created_at" => DateTime .utc_now () |> DateTime .to_iso8601 (),
2764+ " data" => " value"
2765+ }
2766+ json = Jason .encode! (nested)
2767+ SQL .query! (Repo , " INSERT INTO events (metadata) VALUES (?)" , [json])
2768+
2769+ # ✅ WORKS - Encode before creating map
2770+ dt = DateTime .utc_now () |> DateTime .to_iso8601 ()
2771+ nested = %{" created_at" => dt, " data" => " value" }
2772+ json = Jason .encode! (nested)
2773+ SQL .query! (Repo , " INSERT INTO events (metadata) VALUES (?)" , [json])
2774+ ```
2775+
2776+ ** Workaround:**
2777+ When working with maps/lists containing temporal types, manually convert them to JSON strings before passing to queries:
2778+
2779+ ``` elixir
2780+ defmodule MyApp .JsonHelpers do
2781+ def safe_json_encode (map) when is_map (map) do
2782+ map
2783+ |> Enum .map (fn
2784+ {k, %DateTime {} = v} -> {k, DateTime .to_iso8601 (v)}
2785+ {k, %NaiveDateTime {} = v} -> {k, NaiveDateTime .to_iso8601 (v)}
2786+ {k, %Date {} = v} -> {k, Date .to_iso8601 (v)}
2787+ {k, %Decimal {} = v} -> {k, Decimal .to_string (v)}
2788+ {k, v} -> {k, v}
2789+ end )
2790+ |> Enum .into (%{})
2791+ |> Jason .encode! ()
2792+ end
2793+ end
2794+
2795+ # Usage:
2796+ nested = %{
2797+ " created_at" => DateTime .utc_now (),
2798+ " data" => " value"
2799+ }
2800+ json = MyApp .JsonHelpers .safe_json_encode (nested)
2801+ SQL .query! (Repo , " INSERT INTO events (metadata) VALUES (?)" , [json])
2802+ ```
2803+
2804+ ---
2805+
26132806### Limitations and Known Issues
26142807
26152808#### freeze_replica/1 - NOT SUPPORTED
0 commit comments