@@ -89,8 +89,13 @@ defmodule EctoLibSql.JSON do
8989
9090 ## Returns
9191
92- - `{:ok, value}` - Extracted value, or nil if path doesn't exist
93- - `{:error, reason}` on failure
92+ The return type depends on the extracted JSON value:
93+ - `{:ok, string}` - For JSON text values (e.g., "dark")
94+ - `{:ok, integer}` - For JSON integer values (e.g., 30)
95+ - `{:ok, float}` - For JSON real/float values (e.g., 99.99)
96+ - `{:ok, nil}` - For JSON null values or non-existent paths
97+ - `{:ok, json_text}` - For JSON objects/arrays, returned as JSON text string
98+ - `{:error, reason}` - On query failure
9499
95100 ## Examples
96101
@@ -100,14 +105,22 @@ defmodule EctoLibSql.JSON do
100105 {:ok, age} = EctoLibSql.JSON.extract(state, ~s({"user":{"age":30}}), "$.user.age")
101106 # Returns: {:ok, 30}
102107
108+ {:ok, items} = EctoLibSql.JSON.extract(state, ~s({"items":[1,2,3]}), "$.items")
109+ # Returns: {:ok, "[1,2,3]"} (JSON array as text)
110+
111+ {:ok, nil} = EctoLibSql.JSON.extract(state, ~s({"a":1}), "$.missing")
112+ # Returns: {:ok, nil} (path doesn't exist)
113+
103114 ## Notes
104115
105- - Returns JSON types as-is (objects and arrays returned as JSON text)
106- - Use json_extract to preserve JSON structure, or ->> operator to convert to SQL types
107- - Works with both text JSON and JSONB binary format
116+ - JSON objects and arrays are returned as JSON text strings
117+ - Use `-> operator in SQL queries to preserve JSON structure, or `->> operator to convert to SQL types
118+ - Works with both text JSON and JSONB binary format (format conversion is automatic)
119+ - For nested JSON structures, you can chain extractions or use JSON paths like "$.user.address.city"
108120
109121 """
110- @ spec extract ( State . t ( ) , String . t ( ) | binary , String . t ( ) ) :: { :ok , term ( ) } | { :error , term ( ) }
122+ @ spec extract ( State . t ( ) , String . t ( ) | binary , String . t ( ) ) ::
123+ { :ok , String . t ( ) | integer ( ) | float ( ) | nil } | { :error , term ( ) }
111124 def extract ( % State { } = state , json , path ) when is_binary ( json ) and is_binary ( path ) do
112125 Native . query_args (
113126 state . conn_id ,
@@ -509,8 +522,19 @@ defmodule EctoLibSql.JSON do
509522 end
510523
511524 # Private helpers: Result handling patterns
512- # All Native.query_args/5 calls return a response map that needs handling.
525+ # All Native.query_args/5 calls return a response map from Rust that needs handling.
513526 # These helpers reduce duplication and provide consistent error handling.
527+ #
528+ # Expected response shape from Native.query_args/5:
529+ # %{
530+ # "columns" => [list of column names],
531+ # "rows" => [list of result rows, where each row is a list of values],
532+ # "num_rows" => total number of rows
533+ # }
534+ #
535+ # Error responses are:
536+ # %{"error" => reason_string} (from Rust)
537+ # {:error, reason} (from Elixir error handling)
514538
515539 @ doc false
516540 defp handle_single_result ( response ) do
@@ -702,7 +726,8 @@ defmodule EctoLibSql.JSON do
702726 # Returns: {:ok, "{\" a\" :1,\" c\" :3}"}
703727
704728 {:ok, json} = EctoLibSql.JSON.remove(state, ~s([1,2,3,4,5]), ["$[0]", "$[2]"])
705- # Returns: {:ok, "[2,4,5]"}
729+ # Returns: {:ok, "[2,3,5]"}
730+ # Note: Paths are removed in order; after removing $[0], the original $[2] is now at $[1]
706731
707732 """
708733 @ spec remove ( State . t ( ) , String . t ( ) | binary , String . t ( ) | [ String . t ( ) ] ) ::
@@ -845,26 +870,52 @@ defmodule EctoLibSql.JSON do
845870 end
846871
847872 @ doc """
848- Apply a JSON patch to modify JSON.
873+ Apply a JSON Merge Patch to modify JSON (RFC 7396) .
849874
850- The patch is itself a JSON object where keys are paths and values are the updates to apply.
851- Effectively performs multiple set/replace operations in one call.
875+ Implements RFC 7396 JSON Merge Patch semantics. The patch is a JSON object where:
876+ - **Top-level keys** are object keys in the target, not JSON paths
877+ - **Values** replace the corresponding object values in the target
878+ - **Nested objects** are merged recursively
879+ - **null values** remove the key from the target object
880+
881+ To update nested structures, the patch object must reflect the nesting level.
852882
853883 ## Parameters
854884
855885 - state: Connection state
856- - json: JSON text or JSONB binary data
857- - patch: JSON patch object (keys are paths, values are replacements )
886+ - json: JSON text or JSONB binary data (must be an object)
887+ - patch: JSON object with merge patch semantics (keys are object keys, not paths )
858888
859889 ## Returns
860890
861- - `{:ok, modified_json}` - JSON after applying patch
891+ - `{:ok, modified_json}` - JSON after applying merge patch
862892 - `{:error, reason}` on failure
863893
864894 ## Examples
865895
866- {:ok, json} = EctoLibSql.JSON.patch(state, ~s({"a":1,"b":2}), ~s({"$.a":10,"$.c":3}))
867- # Returns: {:ok, "{\" a\" :10,\" b\" :2,\" c\" :3}"}
896+ # Top-level key replacement
897+ {:ok, json} = EctoLibSql.JSON.patch(state, ~s({"a":1,"b":2}), ~s({"a":10}))
898+ # Returns: {:ok, "{\" a\" :10,\" b\" :2}"}
899+
900+ # Add new top-level key
901+ {:ok, json} = EctoLibSql.JSON.patch(state, ~s({"a":1,"b":2}), ~s({"c":3}))
902+ # Returns: {:ok, "{\" a\" :1,\" b\" :2,\" c\" :3}"}
903+
904+ # Remove key with null
905+ {:ok, json} = EctoLibSql.JSON.patch(state, ~s({"a":1,"b":2,"c":3}), ~s({"b":null}))
906+ # Returns: {:ok, "{\" a\" :1,\" c\" :3}"}
907+
908+ # Nested object merge (replaces entire nested object)
909+ {:ok, json} = EctoLibSql.JSON.patch(state, ~s({"user":{"name":"Alice","age":30}}), ~s({"user":{"age":31}}))
910+ # Returns: {:ok, "{\" user\" :{\" age\" :31}}"} (replaces entire user object, not a deep merge)
911+
912+ ## Notes
913+
914+ - This implements RFC 7396 JSON Merge Patch, NOT RFC 6902 JSON Patch
915+ - Object keys in the patch are literal keys, not JSON paths (use "a" not "$.a")
916+ - For nested structures, the patch replaces the entire value at that key (not a deep recursive merge)
917+ - To perform deep merges or path-based updates, use `json_set/4` or `json_replace/4` instead
918+ - Works with both text JSON and JSONB binary format
868919
869920 """
870921 @ spec patch ( State . t ( ) , String . t ( ) | binary , String . t ( ) | binary ) ::
0 commit comments