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
10 changes: 10 additions & 0 deletions meta/sql-columns.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
{
"$schema": "./sql-columns.schema.json",
"description": "The columns of rows a SQL function returns whose value MEOS does not state. parser/sqlfn.py feeds every column of a returned row from the C value of its type: the value or array the function returns, or an out-parameter. A column its wrapper computes rather than reads is declared here, keyed by the composite type the function returns, or by its SQL name when it returns record; a row with a column neither fed nor declared stops the catalog.",
"composites": {
"geomval": {
"note": "PostGIS's composite of a geometry and a value, created by postgis_raster, which MobilityDB requires when built with RASTER; dumpAsPolygons returns a set of them.",
"columns": [["geom", "geometry"], ["val", "double precision"]]
}
},
"rows": {
"geomval": {
"note": "A GeomVal holds a polygon and the value of the pixels it covers; the rows of geomval read its geom and its val, as PostGIS's ST_DumpAsPolygons does.",
"columns": {"geom": {"field": "geom"}, "val": {"field": "val"}}
},
"index_tbox": {
"note": "The tiles of a temporal box: index numbers them from 1 in the order the MEOS array returns them.",
"columns": {"index": {"from": "ordinal"}}
Expand Down
27 changes: 27 additions & 0 deletions meta/sql-columns.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,33 @@
"properties": {
"$schema": { "type": "string" },
"description": { "type": "string" },
"composites": {
"type": "object",
"description": "The composite types of another extension a SQL function returns, which the SQL the catalog reads does not create, keyed by type name.",
"additionalProperties": {
"type": "object",
"required": ["note", "columns"],
"additionalProperties": false,
"properties": {
"note": {
"type": "string",
"description": "The extension creating the type, and the functions returning it.",
"minLength": 1
},
"columns": {
"type": "array",
"description": "The (name, SQL type) of each member, in order.",
"minItems": 1,
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": { "type": "string", "minLength": 1 }
}
}
}
}
},
"rows": {
"type": "object",
"description": "Keyed by the composite type a function returns, or by its SQL name when it returns record.",
Expand Down
16 changes: 14 additions & 2 deletions parser/sqlfn.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,9 @@ def sql_statements(sql_src):
"""(statements, type vocabulary, composite types) of the CREATE FUNCTION statements
under `sql_src`, each statement as #_create_fn_stmts yields it with its body. The
vocabulary holds every RETURNS type and every single-token argument type, so an
argument's type is read against it (#_arg_type)."""
stmts, vocab, composites = [], set(), {}
argument's type is read against it (#_arg_type). The composite types are those the
SQL creates and those of another extension a function returns (#declared_composites)."""
stmts, vocab, composites = [], set(), dict(declared_composites())
sql_src = Path(sql_src)
if not sql_src.exists():
return stmts, vocab, composites
Expand Down Expand Up @@ -514,6 +515,17 @@ def declared_columns(path=_COLUMNS_META):
return {key: entry["columns"] for key, entry in doc["rows"].items()}


def declared_composites(path=_COLUMNS_META):
"""The composite types of another extension a SQL function returns, stated in
`meta/sql-columns.json` beside the rows #declared_columns reads, since the SQL the
catalog reads creates none of them; {typeName: [(column, type), ...]} as
#_composite_types yields the ones it creates: PostGIS's `geomval`, the rows of
`dumpAsPolygons`."""
doc = json.loads(Path(path).read_text())
return {key: [tuple(c) for c in entry["columns"]]
for key, entry in (doc.get("composites") or {}).items()}


def _c_base(ctype):
"""A C type without `const`, `struct` and its pointer levels, with the
`<stdint.h>` spellings read as MEOS's own (`int64_t` is `int64`), and the number of
Expand Down
27 changes: 26 additions & 1 deletion tests/test_sqlfn_rows.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from pathlib import Path

from parser.sqlfn import (_wrapper_sql_sigs, attach_row_sources, attach_sqlfn_map,
declared_columns)
declared_columns, declared_composites)

ROOT = Path(__file__).resolve().parents[1]
COLUMNS = ROOT / "meta" / "sql-columns.json"
Expand Down Expand Up @@ -75,6 +75,11 @@
RETURNS SETOF integer
AS 'MODULE_PATHNAME', 'Set_unnest'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
CREATE FUNCTION dumpAsPolygons(rast raster, band integer DEFAULT 1,
exclude_nodata_value boolean DEFAULT true)
RETURNS SETOF geomval
AS 'MODULE_PATHNAME', 'Raster_dump_as_polygons'
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
"""


Expand Down Expand Up @@ -105,6 +110,13 @@ def test_a_composite_return_type_gives_its_members(self):
self.assertEqual(s["columns"], [("index", "integer"), ("tile", "tbox")])
self.assertEqual(s["argDefaults"], [None, None, "'2000-01-03'"])

def test_a_composite_of_another_extension_gives_its_declared_members(self):
"""As #test_a_composite_return_type_gives_its_members, for a type the SQL does
not create: geomval's members are those meta/sql-columns.json states for it."""
s = _sigs()["Raster_dump_as_polygons"][0]
self.assertEqual((s["ret"], s["retSet"]), ("geomval", True))
self.assertEqual(s["columns"], [("geom", "geometry"), ("val", "double precision")])

def test_one_value_has_no_columns(self):
self.assertIsNone(_sigs()["Set_unnest"][0]["columns"])

Expand Down Expand Up @@ -409,6 +421,19 @@ def test_the_declarations_are_read_by_row(self):
self.assertEqual(declared["index_tbox"], {"index": {"from": "ordinal"}})
self.assertEqual(declared["tDisjointPairs"], PAIRS_OFFSET)

def test_the_composites_are_read_by_type(self):
"""As #test_the_declarations_are_read_by_row reads the rows."""
self.assertEqual(declared_composites(COLUMNS)["geomval"],
[("geom", "geometry"), ("val", "double precision")])

def test_a_composite_member_without_a_type_is_refused(self):
"""As #test_an_unknown_source_is_refused refuses a row's column."""
import jsonschema
doc = json.loads(COLUMNS.read_text())
doc["composites"]["geomval"]["columns"][0] = ["geom"]
with self.assertRaises(jsonschema.ValidationError):
jsonschema.validate(doc, json.loads(SCHEMA.read_text()))


if __name__ == "__main__":
unittest.main()
Loading