diff --git a/query/src/org/labkey/query/controllers/LabKeySql.md b/query/src/org/labkey/query/controllers/LabKeySql.md index cab04cd39e8..b02b11d1261 100644 --- a/query/src/org/labkey/query/controllers/LabKeySql.md +++ b/query/src/org/labkey/query/controllers/LabKeySql.md @@ -231,4 +231,109 @@ Here is a summary of the available functions and methods in LabKey SQL. * `moduleProperty(module name, property name)`: Returns a module property. * `overlaps(START1, END1, START2, END2)`: Tests for overlapping time intervals (PostgreSQL only). * `userid()`, `username()`: Return user information. -* `version()`: Returns the current schema version. \ No newline at end of file +* `version()`: Returns the current schema version. + +----- + +### **9. JSON and JSONB Operators and Functions (PostgreSQL Only)** + +LabKey SQL supports PostgreSQL JSON and JSONB operators and functions for working with JSON data stored in columns. These are **not available on MS SQL Server**. LabKey SQL does not natively understand arrays, but functions that expect them may still work. See the [PostgreSQL docs](https://www.postgresql.org/docs/14/functions-json.html) for detailed usage. + +#### **Operators via `json_op`** + +Native PostgreSQL operator syntax (`->`, `->>`, etc.) cannot be used directly in LabKey SQL. Instead, use the `json_op` pass-through function with three arguments: the left operand, the operator as a string, and the right operand. + +* **Supported operators:** `->`, `->>`, `#>`, `#>>`, `@>`, `<@`, `?`, `?|`, `?&`, `||`, `-`, `#-` +* **Syntax:** `json_op(left_operand, 'operator', right_operand)` +* **Examples:** + * **Extract by key (as JSON):** Get a nested value from a JSONB column: + ```sql + SELECT json_op(metadata, '->', 'name') AS name_json FROM samples + ``` + * **Extract by key (as text):** Get the text value: + ```sql + SELECT json_op(metadata, '->>', 'name') AS name_text FROM samples + ``` + * **Containment check:** Filter rows where JSONB contains a given structure: + ```sql + SELECT * FROM samples WHERE json_op(metadata, '@>', parse_jsonb('{"status":"active"}')) + ``` + * **Key existence check:** + ```sql + SELECT * FROM samples WHERE json_op(metadata, '?', 'name') + ``` + +#### **Conversion / Parsing Functions** + +* `parse_json(text)`, `parse_jsonb(text)`: Cast a text value to JSON or JSONB. Use instead of `::jsonb` or `CAST(... AS JSONB)`. + ```sql + SELECT parse_jsonb('{"a":1, "b":null}') + ``` +* `to_json(value)`, `to_jsonb(value)`: Convert a value to JSON/JSONB. Text values become a single JSON string. +* `array_to_json(array)`: Convert an array to JSON. +* `row_to_json(value)`: Convert a scalar row to JSON. **Note:** Does not support converting an entire table to JSON; use `to_jsonb()` instead. + +#### **Builder Functions** + +* `json_build_array(...)`, `jsonb_build_array(...)`: Build a JSON array from arguments. + ```sql + SELECT jsonb_build_array(1, 'two', 3.0) + ``` +* `json_build_object(...)`, `jsonb_build_object(...)`: Build a JSON object from key/value arguments. + ```sql + SELECT jsonb_build_object('name', sample_name, 'type', sample_type) FROM samples + ``` +* `json_object(text_array)`, `jsonb_object(text_array)`: Build a JSON object from a text array. + +#### **Query and Extraction Functions** + +* `json_array_length(json)`, `jsonb_array_length(jsonb)`: Return the length of the outermost JSON array. +* `json_each(json)`, `jsonb_each(jsonb)`: Expand the outermost JSON object into key/value pairs. **Note:** Only scalar function usage is supported, not the table-returning version. + ```sql + SELECT json_each('{"a":"foo", "b":"bar"}') AS Value + ``` +* `json_each_text(json)`, `jsonb_each_text(jsonb)`: Like `json_each` but values are returned as text. Only scalar usage supported. +* `json_extract_path(json, ...)`, `jsonb_extract_path(jsonb, ...)`: Return the JSON value at the given path. + ```sql + SELECT jsonb_extract_path(metadata, 'address', 'city') FROM samples + ``` +* `json_extract_path_text(json, ...)`, `jsonb_extract_path_text(jsonb, ...)`: Return the value at the given path as text. +* `json_object_keys(json)`, `jsonb_object_keys(jsonb)`: Return the keys of the outermost JSON object. +* `json_array_elements(json)`, `jsonb_array_elements(jsonb)`: Expand a JSON array into a set of values. +* `json_array_elements_text(json)`, `jsonb_array_elements_text(jsonb)`: Expand a JSON array into a set of text values. + +#### **Type Inspection and Cleanup Functions** + +* `json_typeof(json)`, `jsonb_typeof(jsonb)`: Return the type of the outermost JSON value (e.g., `"object"`, `"array"`, `"string"`, `"number"`). +* `json_strip_nulls(json)`, `jsonb_strip_nulls(jsonb)`: Remove all null-valued keys from a JSON object. + +#### **Modification Functions** + +* `jsonb_insert(jsonb, path, new_value)`: Insert a value at a given path within a JSONB object. +* `jsonb_pretty(jsonb)`: Format a JSONB value as indented text. +* `jsonb_set(jsonb, path, new_value)`: Set the value at a given path. Strict: returns NULL on NULL input. +* `jsonb_set_lax(jsonb, path, new_value, null_behavior)`: Like `jsonb_set` but not strict. The `null_behavior` argument must be one of: `'raise_exception'`, `'use_json_null'`, `'delete_key'`, or `'return_target'`. + +#### **Path Query Functions** + +* `jsonb_path_exists(jsonb, path)`, `jsonb_path_exists_tz(...)`: Check whether the JSON path returns any item. The `_tz` variant is timezone-aware. +* `jsonb_path_match(jsonb, path)`, `jsonb_path_match_tz(...)`: Return the result of a JSON path predicate check. +* `jsonb_path_query(jsonb, path)`, `jsonb_path_query_tz(...)`: Return all items matched by the JSON path. +* `jsonb_path_query_array(jsonb, path)`, `jsonb_path_query_array_tz(...)`: Return matched items as an array. +* `jsonb_path_query_first(jsonb, path)`, `jsonb_path_query_first_tz(...)`: Return the first matched item. + +#### **Not Supported** + +The following functions are **not supported** in LabKey SQL: +`json_populate_record`, `jsonb_populate_record`, `json_populate_recordset`, `jsonb_populate_recordset`, `json_to_record`, `jsonb_to_record`, `json_to_recordset`, `jsonb_to_recordset`. + +#### **Quick Reference for Writing LabKey SQL with JSON** + +When writing LabKey SQL queries that work with JSON columns: + +1. **Always use `json_op()` for operators** — never use raw PostgreSQL operator syntax like `->` or `->>`. Wrap them: `json_op(col, '->>', 'key')`. +2. **Use `parse_jsonb()` to create JSONB literals** — there is no `::jsonb` cast in LabKey SQL. Write `parse_jsonb('{"key":"value"}')`. +3. **Use `jsonb_extract_path_text()` for nested field access** — this is often the clearest way to extract a deeply nested text value: `jsonb_extract_path_text(col, 'level1', 'level2', 'field')`. +4. **Use `jsonb_build_object()` to construct JSON** — for building JSON from column values: `jsonb_build_object('id', rowid, 'name', label)`. +5. **Check database type first** — these functions only work on PostgreSQL. If the target server may use MS SQL Server, do not use them. +6. **The `validateSQL` MCP tool can verify syntax** — use it to check JSON function calls before the user saves a query. \ No newline at end of file