Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions meta/sql-columns.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
"tTouchesPairs": {
"note": "i and j are positions in the SQL arrays, from 1; MEOS returns the C indices, from 0.",
"columns": {"i": {"offset": 1}, "j": {"offset": 1}}
},
"jsonbEachText": {
"note": "The key and the value are both text: the key is the array jsonb_each_text returns, the value its out-parameter values.",
"columns": {"key": {"from": "return"}, "value": {"from": "values"}}
}
}
}
5 changes: 3 additions & 2 deletions meta/sql-columns.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@
"additionalProperties": false,
"properties": {
"from": {
"description": "ordinal: the column numbers the rows from 1, as PostgreSQL's WITH ORDINALITY.",
"const": "ordinal"
"description": "ordinal: the column numbers the rows from 1, as PostgreSQL's WITH ORDINALITY. return, or the name of an out-parameter: the C value feeding the column, where two C values of the column's type leave its source to this statement.",
"type": "string",
"pattern": "^(ordinal|return|[a-z_][a-z0-9_]*)$"
},
"offset": {
"description": "A constant the wrapper adds to the C value feeding the column.",
Expand Down
22 changes: 20 additions & 2 deletions parser/sqlfn.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,14 +589,32 @@ def _column_sources(func, sig, sqlc, declared, struct):
`meta/sql-columns.json` states what only the wrapper does: a column numbering
the rows from 1, as PostgreSQL's WITH ORDINALITY (`"from": "ordinal"`, the index
of a tile), and a constant the wrapper adds (`"offset": 1`, turning a C array
index into a SQL array position)."""
index into a SQL array position). It also states the C value feeding a column
where two C values of the column's type fit it (`"from": "return"` or the name
of an out-parameter, the key and the value of `jsonbEachText`); the column
takes that value, and a statement naming no C value of the function stops the
catalog as a column nothing feeds does."""
stated = declared.get(sig["ret"]) or declared.get(sig.get("sqlName") or func.get("sqlfn")) or {}
cols = sig["columns"]
fed = [c for c in cols if "from" not in stated.get(c["name"], {})]
fed = [c for c in cols if stated.get(c["name"], {}).get("from") != "ordinal"]
classes = {c for cs in sqlc.values() for c in cs}
slots = _row_slots(func, sig.get("retSet", False), len(fed), struct, classes)
used, out = set(), {}
# A column stated with the C value feeding it takes that value: two C values of
# one type leave the source of their columns to the statement, never to order.
for c in fed:
src = stated.get(c["name"], {}).get("from")
if src is None:
continue
named = [k for k, (slot, _, _) in enumerate(slots)
if slot["from"] == src and k not in used]
if len(named) != 1:
return None
used.add(named[0])
out[c["name"]] = dict(slots[named[0]][0])
for c in fed:
if c["name"] in out:
continue
fits = [k for k, (_, base, stars) in enumerate(slots)
if k not in used and _fits(c["type"], base, stars, sqlc)]
if not fits or len({slots[k][0]["from"] for k in fits}) > 1:
Expand Down
38 changes: 37 additions & 1 deletion tests/test_sqlfn_rows.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,21 @@ def _attach(func, declared=None):
("tnumber", "tbigint"))}],
}

# A row whose two columns are fed by two C values of one type, the returned keys
# and the out-parameter values, shaped as #VALUE_TIME_SPLIT is.
EACH_TEXT = {
"name": "jsonb_each_text",
"sqlfn": "jsonbEachText",
"params": [{"name": "jb", "cType": "const Jsonb *"},
{"name": "values", "cType": "text **"},
_count()],
"returnType": {"c": "text **"},
"shape": {"arrayReturn": {"element": {"c": "text *"}},
"outParams": ["values", "count"]},
"sqlSignatures": [{"args": ["jsonb"], "ret": "record", "retSet": True,
"columns": _cols(("key", "text"), ("value", "text"))}],
}


def _pairs(with_periods):
params = [{"name": "arr1", "cType": "const Temporal **"},
Expand Down Expand Up @@ -315,6 +330,27 @@ def test_an_undeclared_offset_is_not_invented(self):
cols, _ = _attach(_pairs(False))
self.assertNotIn("offset", cols[0])

def test_two_c_values_of_one_type_need_a_statement(self):
"""As #test_a_column_two_c_values_fit_stops_the_catalog, over EACH_TEXT."""
with self.assertRaises(ValueError):
_attach(copy.deepcopy(EACH_TEXT))

def test_a_stated_source_naming_no_c_value_stops_the_catalog(self):
"""As #test_a_column_no_c_value_fits_stops_the_catalog, for a statement."""
with self.assertRaises(ValueError):
_attach(copy.deepcopy(EACH_TEXT),
{"jsonbEachText": {"key": {"from": "return"},
"value": {"from": "vals"}}})

def test_a_stated_source_feeds_its_column(self):
"""As #test_an_ordinal_column_is_declared_and_a_class_is_one_value states
an ordinal, a statement names the C value of each column of EACH_TEXT."""
cols, _ = _attach(copy.deepcopy(EACH_TEXT),
{"jsonbEachText": {"key": {"from": "return"},
"value": {"from": "values"}}})
self.assertEqual(cols, [{"name": "key", "type": "text", "from": "return"},
{"name": "value", "type": "text", "from": "values"}])


class DeclaredColumnsTests(unittest.TestCase):

Expand All @@ -326,7 +362,7 @@ def test_the_declarations_validate(self):
def test_an_unknown_source_is_refused(self):
import jsonschema
doc = json.loads(COLUMNS.read_text())
doc["rows"]["index_tbox"]["columns"]["index"] = {"from": "position"}
doc["rows"]["index_tbox"]["columns"]["index"] = {"from": "with ordinality"}
with self.assertRaises(jsonschema.ValidationError):
jsonschema.validate(doc, json.loads(SCHEMA.read_text()))

Expand Down
Loading