Skip to content

Commit 432b098

Browse files
authored
Merge pull request #6 from msdemlei/add-moc-casts
Add MOC vs. other geometries casts
2 parents 3a7f241 + d2dc156 commit 432b098

12 files changed

+1005
-10
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
regression.out
99
regression.diffs
1010
tags
11+
buildpod

Diff for: HACKING

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
SQL definitions
2+
===============
3+
4+
Long version:
5+
<https://www.postgresql.org/docs/current/extend-extensions.html>.
6+
7+
If you're writing new features that require SQL support, pick some
8+
descriptive name; let's say my_new_op.
9+
10+
Put your new code into a file called pgs_my_new_op.sql.in. The .in
11+
extension here usually indicates "it's for copying stuff together";
12+
usally, not much processing is done on such files.
13+
14+
Then edit the Makefile. The PGS_SQL variable contains a list of the
15+
SQL files eventually copied together, without the .in. Add your new
16+
file there.
17+
18+
You will also need to create an upgrade file. In order to tell postgres
19+
to execute it, increase PGSPHERE_VERSION as appropriate. As a
20+
consequence, you will have to::
21+
22+
git mv pg_sphere--<old version>.sql.in pg_sphere--<new version>.sql.in
23+
24+
and also to update the version in pg_sphere.control.
25+
26+
Then create a make rule::
27+
28+
pg_sphere--<old version>--<new version>.sql: pgs_my_new_op.sql.in
29+
cat $^ > $@
30+
31+
(of course, this will extend to having multiple sql.in files).
32+
33+
Finally, add the target of that rule to the DATA_built variable.
34+
35+
36+
Regression tests
37+
================
38+
39+
Regressions tests are as per
40+
<https://www.postgresql.org/docs/current/extend-pgxs.html>.
41+
42+
In short, write queries executing your new features into a file
43+
sql/my_new_op.sql, and add "my_new_op" (without the extension or the
44+
directory name) to both REGRESS and TESTS in the Makefile.
45+
46+
Then touch expected/my_new_op.out, run make test. This will of course
47+
fail, because your tests hopefully will output something. But then you
48+
can pick out the diff from
49+
/var/lib/postgresql/pgsphere/regression.diffs, have another critical
50+
look at it and generatoe your .out file from it.

Diff for: Makefile

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
PGSPHERE_VERSION = 1.2.0
1+
PGSPHERE_VERSION = 1.2.1
22

33
# the base dir name may be changed depending on git clone command
44
SRC_DIR = $(shell basename $(shell pwd))
@@ -17,18 +17,19 @@ DATA_built = $(RELEASE_SQL) \
1717
pg_sphere--1.0_gavo--1.1.5beta0gavo.sql \
1818
pg_sphere--1.1.5beta0gavo--1.1.5beta2gavo.sql \
1919
pg_sphere--1.1.5beta2gavo--1.1.5beta4gavo.sql \
20-
pg_sphere--1.1.5beta4gavo--1.2.0.sql
20+
pg_sphere--1.1.5beta4gavo--1.2.0.sql \
21+
pg_sphere--1.2.0--1.2.1.sql
2122

2223
DOCS = README.pg_sphere COPYRIGHT.pg_sphere
2324
REGRESS = init tables points euler circle line ellipse poly path box index \
2425
contains_ops contains_ops_compat bounding_box_gist gnomo healpix \
25-
moc
26+
moc mocautocast
2627

2728
REGRESS_9_5 = index_9.5 # experimental for spoint3
2829

2930
TESTS = init_test tables points euler circle line ellipse poly path box index \
3031
contains_ops contains_ops_compat bounding_box_gist gnomo healpix \
31-
moc
32+
moc mocautocast
3233

3334
ifndef CXXFLAGS
3435
# no support for CXXFLAGS in PGXS before v11
@@ -45,7 +46,8 @@ PGS_SQL = pgs_types.sql pgs_point.sql pgs_euler.sql pgs_circle.sql \
4546
pgs_line.sql pgs_ellipse.sql pgs_polygon.sql pgs_path.sql \
4647
pgs_box.sql pgs_contains_ops.sql pgs_contains_ops_compat.sql \
4748
pgs_gist.sql gnomo.sql \
48-
healpix.sql pgs_gist_spoint3.sql pgs_moc_type.sql pgs_moc_compat.sql pgs_moc_ops.sql
49+
healpix.sql pgs_gist_spoint3.sql pgs_moc_type.sql pgs_moc_compat.sql pgs_moc_ops.sql \
50+
pgs_moc_geo_casts.sql
4951
PGS_SQL_9_5 = pgs_9.5.sql # experimental for spoint3
5052

5153
USE_PGXS = 1
@@ -155,7 +157,7 @@ ifeq ($(pg_version_9_5_plus),y)
155157
else
156158
endif
157159

158-
# local stuff follows here, next will be "beta2"
160+
# local stuff follows here
159161

160162
AUGMENT_GAVO_111 = $(AUGMENT_UNP_111) healpix.sql # for vanilla 1.1.1 users
161163
UPGRADE_GAVO_111 = $(UPGRADE_UNP_COMMON)
@@ -197,6 +199,12 @@ ifeq ($(has_parallel), n)
197199
sed -i -e '/PARALLEL/d' $@ # version $(pg_version) does not have support for PARALLEL
198200
endif
199201

202+
pg_sphere--1.2.0--1.2.1.sql: pgs_moc_geo_casts.sql.in
203+
cat $^ > $@
204+
ifeq ($(has_parallel), n)
205+
sed -i -e '/PARALLEL/d' $@ # version $(pg_version) does not have support for PARALLEL
206+
endif
207+
200208
# end of local stuff
201209

202210
sscan.o : sparse.c

Diff for: doc/functions.sgm

+28
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,34 @@
638638
</programlisting>
639639
</example>
640640

641+
<funcsynopsis>
642+
<funcprototype>
643+
<funcdef><function>max_order</function></funcdef>
644+
<paramdef>smoc</paramdef>
645+
</funcprototype>
646+
</funcsynopsis>
647+
<para>
648+
Returns the maximal order of an smoc.
649+
</para>
650+
<para>
651+
The maximal order of a MOC can be higher than than the highest
652+
order appearing in the serialisation. For instance, in the example
653+
below the full-sky coverage given at order 6 means that no patch
654+
larger than about 1 degree is not covered. In the ASCII
655+
serialisation, give the maximum order with an with an empty cell
656+
list.
657+
</para>
658+
659+
<example>
660+
<title>Obtaining a MOC order</title>
661+
<programlisting>
662+
<![CDATA[SELECT max_order(smoc('0/0-11 6/')) as order;]]>
663+
<![CDATA[ order]]>
664+
<![CDATA[-----------]]>
665+
<![CDATA[ 6 ]]>
666+
</programlisting>
667+
</example>
668+
641669
</sect2>
642670

643671
</sect1>

Diff for: doc/gen_moccast.py

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# A script to create the automatic casts for overlaps and intersects
2+
# between MOCs and spolys/scircles.
3+
#
4+
# This has originally been used to create pg_sphere--1.2.0--1.2.1.sql.
5+
# Before 1.2.1 is release, this can be fixed to improve that SQL.
6+
# After the 1.2.1 release, this is just documentation on how MOC
7+
# casts were generated that is perhaps just a bit more readable than
8+
# that bunch of SQL.
9+
10+
import datetime
11+
import re
12+
import sys
13+
14+
15+
OVERLAP_DEFS = [
16+
# func_stem, operator, commutator
17+
('subset', '<@', '@>'),
18+
('not_subset', '!<@', '!@>'),
19+
('superset', '@>', '<@'),
20+
('not_superset', '!@>', '!<@'),
21+
]
22+
23+
INTERSECT_DEFS = [
24+
# func_stem, operator, commutator
25+
('intersect', '&&', '&&'),
26+
('not_intersect', '!&&', '!&&'),
27+
]
28+
29+
30+
GEO_TYPES = ["scircle", "spoly"]
31+
32+
OP_DEFS = OVERLAP_DEFS
33+
34+
35+
class Accum:
36+
"""an accumulator for our output.
37+
"""
38+
def __init__(self):
39+
self.parts = []
40+
41+
@property
42+
def content(self):
43+
return "".join(self.parts)
44+
45+
def write(self, s):
46+
self.parts.append(s)
47+
48+
def writeln(self, *strings):
49+
self.parts.append("\n".join(strings)+"\n")
50+
51+
def replace_last(self, subs):
52+
"""replaces the last non-whitespace char with the string subs.
53+
"""
54+
for index, part in enumerate(reversed(self.parts)):
55+
if part.strip():
56+
break
57+
else:
58+
# nothing to replace
59+
return
60+
61+
index = -1-index
62+
self.parts[index] = re.sub("[^\s](\s*)$",
63+
lambda mat: subs+mat.group(1),
64+
self.parts[index])
65+
66+
def introduce_section(self, sec_name):
67+
self.writeln()
68+
self.writeln("-- #################################")
69+
self.writeln(f"-- {sec_name}")
70+
71+
72+
def emit_drop_code(accum):
73+
accum.introduce_section("Cleanup")
74+
75+
accum.writeln("DROP OPERATOR IF EXISTS")
76+
for _, op, _ in OP_DEFS:
77+
for geo_type in GEO_TYPES:
78+
accum.writeln(f" {op} (smoc, {geo_type}),")
79+
accum.writeln(f" {op} ({geo_type}, smoc),")
80+
accum.replace_last(";")
81+
82+
83+
def make_negator(op):
84+
if op.startswith("!"):
85+
return op[1:]
86+
else:
87+
return "!"+op
88+
89+
90+
def emit_op_def(accum, operator, leftarg, rightarg, procedure, commutator):
91+
accum.writeln(
92+
f"CREATE OPERATOR {operator} (",
93+
f" LEFTARG = {leftarg},",
94+
f" RIGHTARG = {rightarg},",
95+
f" PROCEDURE = {procedure},",
96+
f" COMMUTATOR = '{commutator}',",
97+
f" NEGATOR = '{make_negator(operator)}',",
98+
f" RESTRICT = contsel,",
99+
f" JOIN = contjoinsel",
100+
f");")
101+
102+
103+
def emit_op_and_func(accum, op_def):
104+
func_stem, operator, commutator = op_def
105+
for geo_type in GEO_TYPES:
106+
func_name = f"{geo_type}_{func_stem}_smoc"
107+
accum.writeln(
108+
f"CREATE OR REPLACE FUNCTION {func_name}(",
109+
f" geo_arg {geo_type}, a_moc smoc) RETURNS BOOL AS $body$",
110+
f" SELECT smoc(max_order(a_moc), geo_arg) {operator} a_moc",
111+
f" $body$ LANGUAGE SQL STABLE;")
112+
emit_op_def(accum, operator,
113+
geo_type, "smoc",
114+
func_name,
115+
commutator)
116+
117+
accum.writeln()
118+
119+
func_name = f"smoc_{func_stem}_{geo_type}"
120+
accum.writeln(
121+
f"CREATE OR REPLACE FUNCTION {func_name}(",
122+
f" a_moc smoc, geo_arg {geo_type}) RETURNS BOOL AS $body$",
123+
f" SELECT a_moc {operator} smoc(max_order(a_moc), geo_arg)",
124+
f" $body$ LANGUAGE SQL STABLE;")
125+
emit_op_def(accum, operator,
126+
"smoc", geo_type,
127+
func_name,
128+
commutator)
129+
130+
accum.writeln()
131+
132+
133+
def main():
134+
accum = Accum()
135+
136+
accum.writeln("-- MOC/geometry automatic casts.")
137+
accum.writeln(f"-- Generated {datetime.date.today()} by {sys.argv[0]}.")
138+
accum.writeln(f"-- Re-generation needs to be triggered manually.")
139+
accum.writeln()
140+
emit_drop_code(accum)
141+
142+
accum.introduce_section(" smoc/geo OVERLAPS")
143+
for op_def in OVERLAP_DEFS:
144+
emit_op_and_func(accum, op_def)
145+
accum.writeln()
146+
147+
accum.introduce_section(" smoc/geo INTERSECTS")
148+
for op_def in INTERSECT_DEFS:
149+
emit_op_and_func(accum, op_def)
150+
accum.writeln()
151+
152+
print(accum.content)
153+
154+
155+
if __name__=="__main__":
156+
main()

Diff for: doc/operators.sgm

+44-1
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,19 @@
231231
<para>
232232
An overlap or contain operator does not exist for all
233233
combinations of data types.
234-
For instance, scircle <@ <type>spoint</type> is
234+
For instance, scircle &lt;@ <type>spoint</type> is
235235
useless because a spherical point can never contain a
236236
spherical circle.
237237
</para>
238+
<para>
239+
When one of the arguments of such an operator is a MOC
240+
and the other is an scircle or an spoly, the non-MOC argument
241+
is converted to a MOC of the order of the maximum order of
242+
the MOC. When comparing against a MOC-valued column, it
243+
is usually much faster to explicitly convert the geometry
244+
using the smoc constructor, as the conversion will then
245+
only happen once.
246+
</para>
238247
<example>
239248
<title>Is the left circle contained by the right circle?</title>
240249
<programlisting>
@@ -255,6 +264,40 @@
255264
<![CDATA[(1 row)]]>
256265
</programlisting>
257266
</example>
267+
268+
<example>
269+
<title>Overlaps between a circle and a moc</title>
270+
<programlisting>
271+
<![CDATA[sql> SELECT scircle '<(37d, 5d), 0.25d>' <@ smoc('4/1117') AS test ;]]>
272+
<![CDATA[ test]]>
273+
<![CDATA[------]]>
274+
<![CDATA[ f]]>
275+
<![CDATA[(1 row)]]>
276+
</programlisting>
277+
</example>
278+
279+
<example>
280+
<title>Overlaps between a circle and a moc with explicit order</title>
281+
<programlisting>
282+
<![CDATA[sql> SELECT scircle '<(37d, 5d), 0.25d>' <@ smoc('4/1117 5/') AS test ;]]>
283+
<![CDATA[ test]]>
284+
<![CDATA[------]]>
285+
<![CDATA[ t]]>
286+
<![CDATA[(1 row)]]>
287+
</programlisting>
288+
</example>
289+
290+
<example>
291+
<title>Overlaps between a circle and a moc with explicit cast (normally faster)</title>
292+
<programlisting>
293+
<![CDATA[sql> SELECT smoc(5, scircle '<(37d, 5d), 0.25d>') <@ smoc('4/1117 5/') AS test ;]]>
294+
<![CDATA[ test]]>
295+
<![CDATA[------]]>
296+
<![CDATA[ t]]>
297+
<![CDATA[(1 row)]]>
298+
</programlisting>
299+
</example>
300+
258301
</sect2>
259302

260303
<sect2 id="op.cross">

Diff for: expected/init_test.out

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ psql:pg_sphere.test.sql:8569: NOTICE: argument type pointkey is only a shell
3333
psql:pg_sphere.test.sql:8575: NOTICE: argument type pointkey is only a shell
3434
psql:pg_sphere.test.sql:8581: NOTICE: argument type pointkey is only a shell
3535
psql:pg_sphere.test.sql:8587: NOTICE: argument type pointkey is only a shell
36-
psql:pg_sphere.test.sql:9152: NOTICE: return type smoc is only a shell
37-
psql:pg_sphere.test.sql:9158: NOTICE: argument type smoc is only a shell
36+
psql:pg_sphere.test.sql:9154: NOTICE: return type smoc is only a shell
37+
psql:pg_sphere.test.sql:9160: NOTICE: argument type smoc is only a shell

0 commit comments

Comments
 (0)