agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH v1] popcount 4+ messages / 2 participants [nested] [flat]
* [PATCH v1] popcount @ 2020-12-30 10:51 David Fetter <david@fetter.org> 0 siblings, 0 replies; 4+ messages in thread From: David Fetter @ 2020-12-30 10:51 UTC (permalink / raw) Now it's accessible to SQL for the BIT VARYING and BYTEA types. diff --git doc/src/sgml/func.sgml doc/src/sgml/func.sgml index 5021ac1ca9..439a3a4a06 100644 --- doc/src/sgml/func.sgml +++ doc/src/sgml/func.sgml @@ -4069,6 +4069,23 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three'); </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>popcount</primary> + </indexterm> + <function>popcount</function> ( <parameter>bytes</parameter> <type>bytea</type> ) + <returnvalue>integer</returnvalue> + </para> + <para> + Counts the number of bits set in a binary string. + </para> + <para> + <literal>popcount('\xdeadbeef'::bytea)</literal> + <returnvalue>24</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <indexterm> @@ -4830,6 +4847,24 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three'); <returnvalue>101010001010101010</returnvalue> </para></entry> </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>popcount</primary> + </indexterm> + <function>popcount</function> ( <parameter>bits</parameter> <type>bit</type> ) + <returnvalue>int4</returnvalue> + </para> + <para> + Counts the bits set in a bit string. + </para> + <para> + <literal>popcount(B'101010101010101010')</literal> + <returnvalue>9</returnvalue> + </para></entry> + </row> + </tbody> </tgroup> </table> diff --git src/include/catalog/pg_proc.dat src/include/catalog/pg_proc.dat index 139f4a08bd..0e6d0636fa 100644 --- src/include/catalog/pg_proc.dat +++ src/include/catalog/pg_proc.dat @@ -1446,6 +1446,9 @@ { oid => '752', descr => 'substitute portion of string', proname => 'overlay', prorettype => 'bytea', proargtypes => 'bytea bytea int4', prosrc => 'byteaoverlay_no_len' }, +{ oid => '8436', descr => 'count set bits', + proname => 'popcount', prorettype => 'int4', proargtypes => 'bytea', + prosrc => 'byteapopcount'}, { oid => '725', proname => 'dist_pl', prorettype => 'float8', proargtypes => 'point line', @@ -3865,6 +3868,9 @@ { oid => '3033', descr => 'set bit', proname => 'set_bit', prorettype => 'bit', proargtypes => 'bit int4 int4', prosrc => 'bitsetbit' }, +{ oid => '8435', descr => 'count set bits', + proname => 'popcount', prorettype => 'int4', proargtypes => 'bit', + prosrc => 'bitpopcount'}, # for macaddr type support { oid => '436', descr => 'I/O', diff --git src/backend/utils/adt/varbit.c src/backend/utils/adt/varbit.c index 3c03459f51..e92d1a7f8f 100644 --- src/backend/utils/adt/varbit.c +++ src/backend/utils/adt/varbit.c @@ -29,6 +29,8 @@ *------------------------------------------------------------------------- */ +#include <math.h> + #include "postgres.h" #include "access/htup_details.h" @@ -36,6 +38,7 @@ #include "libpq/pqformat.h" #include "nodes/nodeFuncs.h" #include "nodes/supportnodes.h" +#include "port/pg_bitutils.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/varbit.h" @@ -1872,3 +1875,24 @@ bitgetbit(PG_FUNCTION_ARGS) else PG_RETURN_INT32(0); } + +/* + * bitpopcount + * + * Returns the number of bits set in a bit array. + * + */ +Datum +bitpopcount(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + bits8 *p; + int len, popcount; + + len = ceil(VARBITLEN(arg1) / (float4)BITS_PER_BYTE); + p = VARBITS(arg1); + + popcount = (int)pg_popcount((const char *)p, len); + + PG_RETURN_INT32(popcount); +} diff --git src/backend/utils/adt/varlena.c src/backend/utils/adt/varlena.c index 9300d19e0c..9e788babd3 100644 --- src/backend/utils/adt/varlena.c +++ src/backend/utils/adt/varlena.c @@ -3415,6 +3415,21 @@ bytea_overlay(bytea *t1, bytea *t2, int sp, int sl) return result; } +/* + * popcount + */ +Datum +byteapopcount(PG_FUNCTION_ARGS) +{ + bytea *t1 = PG_GETARG_BYTEA_PP(0); + int len, result; + + len = VARSIZE_ANY_EXHDR(t1); + result = pg_popcount(VARDATA_ANY(t1), len); + + PG_RETURN_INT32(result); +} + /* * byteapos - * Return the position of the specified substring. diff --git src/test/regress/expected/bit.out src/test/regress/expected/bit.out index a1fab7ebcb..c91ad3d0d5 100644 --- src/test/regress/expected/bit.out +++ src/test/regress/expected/bit.out @@ -681,6 +681,19 @@ SELECT overlay(B'0101011100' placing '001' from 20); 0101011100001 (1 row) +-- Popcount +SELECT popcount(B'0101011100'::bit(10)); + popcount +---------- + 5 +(1 row) + +SELECT popcount(B'1111111111'::bit(10)); + popcount +---------- + 10 +(1 row) + -- This table is intentionally left around to exercise pg_dump/pg_upgrade CREATE TABLE bit_defaults( b1 bit(4) DEFAULT '1001', diff --git src/test/regress/expected/strings.out src/test/regress/expected/strings.out index 298b6c48c2..9780639038 100644 --- src/test/regress/expected/strings.out +++ src/test/regress/expected/strings.out @@ -2126,3 +2126,10 @@ SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 5 Th\000o\x02\x03 (1 row) +SET bytea_output TO hex; +SELECT popcount(E'\\xdeadbeef'::bytea); + popcount +---------- + 24 +(1 row) + diff --git src/test/regress/sql/bit.sql src/test/regress/sql/bit.sql index 7681d4ab4d..3cd9fb65d3 100644 --- src/test/regress/sql/bit.sql +++ src/test/regress/sql/bit.sql @@ -207,6 +207,10 @@ SELECT overlay(B'0101011100' placing '101' from 6); SELECT overlay(B'0101011100' placing '001' from 11); SELECT overlay(B'0101011100' placing '001' from 20); +-- Popcount +SELECT popcount(B'0101011100'::bit(10)); +SELECT popcount(B'1111111111'::bit(10)); + -- This table is intentionally left around to exercise pg_dump/pg_upgrade CREATE TABLE bit_defaults( b1 bit(4) DEFAULT '1001', diff --git src/test/regress/sql/strings.sql src/test/regress/sql/strings.sql index ad5221ab6b..a35e993ab0 100644 --- src/test/regress/sql/strings.sql +++ src/test/regress/sql/strings.sql @@ -717,3 +717,6 @@ SELECT btrim(E'\\000trim\\000'::bytea, ''::bytea); SELECT encode(overlay(E'Th\\000omas'::bytea placing E'Th\\001omas'::bytea from 2),'escape'); SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 8),'escape'); SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 5 for 3),'escape'); + +SET bytea_output TO hex; +SELECT popcount(E'\\xdeadbeef'::bytea); ^ permalink raw reply [nested|flat] 4+ messages in thread
* [PATCH v5] popcount @ 2020-12-30 10:51 David Fetter <david@fetter.org> 0 siblings, 0 replies; 4+ messages in thread From: David Fetter @ 2020-12-30 10:51 UTC (permalink / raw) Now it's accessible to SQL for the BIT VARYING and BYTEA types. diff --git doc/src/sgml/func.sgml doc/src/sgml/func.sgml index 68fe6a95b4..066431fd3c 100644 --- doc/src/sgml/func.sgml +++ doc/src/sgml/func.sgml @@ -4030,6 +4030,23 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three'); </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>bit_count</primary> + </indexterm> + <function>bit_count</function> ( <parameter>bytes</parameter> <type>bytea</type> ) + <returnvalue>bigint</returnvalue> + </para> + <para> + Counts the number of bits set in a binary string. + </para> + <para> + <literal>bit_count('\xdeadbeef'::bytea)</literal> + <returnvalue>24</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <indexterm> @@ -4830,6 +4847,23 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three'); </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>bit_count</primary> + </indexterm> + <function>bit_count</function> ( <parameter>bits</parameter> <type>bit</type> ) + <returnvalue>bigint</returnvalue> + </para> + <para> + Counts the bits set in a bit string. + </para> + <para> + <literal>bit_count(B'101010101010101010')</literal> + <returnvalue>9</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <indexterm> @@ -4869,6 +4903,7 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three'); <returnvalue>101010001010101010</returnvalue> </para></entry> </row> + </tbody> </tgroup> </table> diff --git src/include/catalog/pg_proc.dat src/include/catalog/pg_proc.dat index e259531f60..feb00eccf9 100644 --- src/include/catalog/pg_proc.dat +++ src/include/catalog/pg_proc.dat @@ -1446,6 +1446,9 @@ { oid => '752', descr => 'substitute portion of string', proname => 'overlay', prorettype => 'bytea', proargtypes => 'bytea bytea int4', prosrc => 'byteaoverlay_no_len' }, +{ oid => '8436', descr => 'count set bits', + proname => 'bit_count', prorettype => 'int8', proargtypes => 'bytea', + prosrc => 'byteapopcount'}, { oid => '725', proname => 'dist_pl', prorettype => 'float8', proargtypes => 'point line', @@ -3876,6 +3879,9 @@ { oid => '3033', descr => 'set bit', proname => 'set_bit', prorettype => 'bit', proargtypes => 'bit int4 int4', prosrc => 'bitsetbit' }, +{ oid => '8435', descr => 'count set bits', + proname => 'bit_count', prorettype => 'int8', proargtypes => 'bit', + prosrc => 'bitpopcount'}, # for macaddr type support { oid => '436', descr => 'I/O', diff --git src/backend/utils/adt/varbit.c src/backend/utils/adt/varbit.c index 2235866244..c9c6c73422 100644 --- src/backend/utils/adt/varbit.c +++ src/backend/utils/adt/varbit.c @@ -36,6 +36,7 @@ #include "libpq/pqformat.h" #include "nodes/nodeFuncs.h" #include "nodes/supportnodes.h" +#include "port/pg_bitutils.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/varbit.h" @@ -1878,3 +1879,30 @@ bitgetbit(PG_FUNCTION_ARGS) else PG_RETURN_INT32(0); } + +/* + * bitpopcount + * + * Returns the number of bits set in a bit string. + * + */ +Datum +bitpopcount(PG_FUNCTION_ARGS) +{ + /* There's really no chance of an overflow here because + * to get to INT64_MAX set bits, an object would have to be + * an exbibyte long, exceeding what PostgreSQL can currently + * store by a factor of 2^28 + */ + int64 popcount; + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + bits8 *p; + int len; + + p = VARBITS(arg1); + len = (VARBITLEN(arg1) + BITS_PER_BYTE - 1) / BITS_PER_BYTE; + + popcount = pg_popcount((char *)p, len); + + PG_RETURN_INT64(popcount); +} diff --git src/backend/utils/adt/varlena.c src/backend/utils/adt/varlena.c index 0bc345aa4d..95091887a9 100644 --- src/backend/utils/adt/varlena.c +++ src/backend/utils/adt/varlena.c @@ -3440,6 +3440,22 @@ bytea_overlay(bytea *t1, bytea *t2, int sp, int sl) return result; } +/* + * popcount + */ +Datum +byteapopcount(PG_FUNCTION_ARGS) +{ + bytea *t1 = PG_GETARG_BYTEA_PP(0); + int len; + int64 result; + + len = VARSIZE_ANY_EXHDR(t1); + result = pg_popcount(VARDATA_ANY(t1), len); + + PG_RETURN_INT64(result); +} + /* * byteapos - * Return the position of the specified substring. diff --git src/test/regress/expected/bit.out src/test/regress/expected/bit.out index a7f95b846d..9b6b3d0c4f 100644 --- src/test/regress/expected/bit.out +++ src/test/regress/expected/bit.out @@ -710,6 +710,19 @@ SELECT overlay(B'0101011100' placing '001' from 20); 0101011100001 (1 row) +-- Popcount +SELECT bit_count(B'0101011100'::bit(10)); + bit_count +----------- + 5 +(1 row) + +SELECT bit_count(B'1111111111'::bit(10)); + bit_count +----------- + 10 +(1 row) + -- This table is intentionally left around to exercise pg_dump/pg_upgrade CREATE TABLE bit_defaults( b1 bit(4) DEFAULT '1001', diff --git src/test/regress/expected/strings.out src/test/regress/expected/strings.out index fb4573d85f..e8c6a99e9d 100644 --- src/test/regress/expected/strings.out +++ src/test/regress/expected/strings.out @@ -2227,3 +2227,10 @@ SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 5 Th\000o\x02\x03 (1 row) +SET bytea_output TO hex; +SELECT bit_count(E'\\xdeadbeef'::bytea); + bit_count +----------- + 24 +(1 row) + diff --git src/test/regress/sql/bit.sql src/test/regress/sql/bit.sql index ea01742c4a..271aa5ea3c 100644 --- src/test/regress/sql/bit.sql +++ src/test/regress/sql/bit.sql @@ -215,6 +215,10 @@ SELECT overlay(B'0101011100' placing '101' from 6); SELECT overlay(B'0101011100' placing '001' from 11); SELECT overlay(B'0101011100' placing '001' from 20); +-- Popcount +SELECT bit_count(B'0101011100'::bit(10)); +SELECT bit_count(B'1111111111'::bit(10)); + -- This table is intentionally left around to exercise pg_dump/pg_upgrade CREATE TABLE bit_defaults( b1 bit(4) DEFAULT '1001', diff --git src/test/regress/sql/strings.sql src/test/regress/sql/strings.sql index 57a48c9d0b..3fb2d66a9f 100644 --- src/test/regress/sql/strings.sql +++ src/test/regress/sql/strings.sql @@ -742,3 +742,6 @@ SELECT btrim(E'\\000trim\\000'::bytea, ''::bytea); SELECT encode(overlay(E'Th\\000omas'::bytea placing E'Th\\001omas'::bytea from 2),'escape'); SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 8),'escape'); SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 5 for 3),'escape'); + +SET bytea_output TO hex; +SELECT bit_count(E'\\xdeadbeef'::bytea); ^ permalink raw reply [nested|flat] 4+ messages in thread
* [PATCH v2] popcount @ 2020-12-30 10:51 David Fetter <david@fetter.org> 0 siblings, 0 replies; 4+ messages in thread From: David Fetter @ 2020-12-30 10:51 UTC (permalink / raw) Now it's accessible to SQL for the BIT VARYING and BYTEA types. diff --git doc/src/sgml/func.sgml doc/src/sgml/func.sgml index 5021ac1ca9..439a3a4a06 100644 --- doc/src/sgml/func.sgml +++ doc/src/sgml/func.sgml @@ -4069,6 +4069,23 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three'); </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>popcount</primary> + </indexterm> + <function>popcount</function> ( <parameter>bytes</parameter> <type>bytea</type> ) + <returnvalue>integer</returnvalue> + </para> + <para> + Counts the number of bits set in a binary string. + </para> + <para> + <literal>popcount('\xdeadbeef'::bytea)</literal> + <returnvalue>24</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <indexterm> @@ -4830,6 +4847,24 @@ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three'); <returnvalue>101010001010101010</returnvalue> </para></entry> </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>popcount</primary> + </indexterm> + <function>popcount</function> ( <parameter>bits</parameter> <type>bit</type> ) + <returnvalue>int4</returnvalue> + </para> + <para> + Counts the bits set in a bit string. + </para> + <para> + <literal>popcount(B'101010101010101010')</literal> + <returnvalue>9</returnvalue> + </para></entry> + </row> + </tbody> </tgroup> </table> diff --git src/include/catalog/pg_proc.dat src/include/catalog/pg_proc.dat index 139f4a08bd..82e3d50114 100644 --- src/include/catalog/pg_proc.dat +++ src/include/catalog/pg_proc.dat @@ -1446,6 +1446,9 @@ { oid => '752', descr => 'substitute portion of string', proname => 'overlay', prorettype => 'bytea', proargtypes => 'bytea bytea int4', prosrc => 'byteaoverlay_no_len' }, +{ oid => '8436', descr => 'count set bits', + proname => 'popcount', prorettype => 'int8', proargtypes => 'bytea', + prosrc => 'byteapopcount'}, { oid => '725', proname => 'dist_pl', prorettype => 'float8', proargtypes => 'point line', @@ -3865,6 +3868,9 @@ { oid => '3033', descr => 'set bit', proname => 'set_bit', prorettype => 'bit', proargtypes => 'bit int4 int4', prosrc => 'bitsetbit' }, +{ oid => '8435', descr => 'count set bits', + proname => 'popcount', prorettype => 'int8', proargtypes => 'bit', + prosrc => 'bitpopcount'}, # for macaddr type support { oid => '436', descr => 'I/O', diff --git src/backend/utils/adt/varbit.c src/backend/utils/adt/varbit.c index 3c03459f51..a71c2b6bf4 100644 --- src/backend/utils/adt/varbit.c +++ src/backend/utils/adt/varbit.c @@ -36,6 +36,7 @@ #include "libpq/pqformat.h" #include "nodes/nodeFuncs.h" #include "nodes/supportnodes.h" +#include "port/pg_bitutils.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/varbit.h" @@ -1872,3 +1873,29 @@ bitgetbit(PG_FUNCTION_ARGS) else PG_RETURN_INT32(0); } + +/* + * bitpopcount + * + * Returns the number of bits set in a bit array. + * + */ +Datum +bitpopcount(PG_FUNCTION_ARGS) +{ + VarBit *arg1 = PG_GETARG_VARBIT_P(0); + bits8 *p; + int len; + int8 popcount; + + /* + * ceil(VARBITLEN(ARG1)/BITS_PER_BYTE) + * done to minimize branches and instructions. + */ + len = (VARBITLEN(arg1) + BITS_PER_BYTE + 1) / BITS_PER_BYTE; + p = VARBITS(arg1); + + popcount = pg_popcount((const char *)p, len); + + PG_RETURN_INT64(popcount); +} diff --git src/backend/utils/adt/varlena.c src/backend/utils/adt/varlena.c index 9300d19e0c..9c5e5b5592 100644 --- src/backend/utils/adt/varlena.c +++ src/backend/utils/adt/varlena.c @@ -3415,6 +3415,22 @@ bytea_overlay(bytea *t1, bytea *t2, int sp, int sl) return result; } +/* + * popcount + */ +Datum +byteapopcount(PG_FUNCTION_ARGS) +{ + bytea *t1 = PG_GETARG_BYTEA_PP(0); + int len; + int8 result; + + len = VARSIZE_ANY_EXHDR(t1); + result = pg_popcount(VARDATA_ANY(t1), len); + + PG_RETURN_INT64(result); +} + /* * byteapos - * Return the position of the specified substring. diff --git src/test/regress/expected/bit.out src/test/regress/expected/bit.out index a1fab7ebcb..c91ad3d0d5 100644 --- src/test/regress/expected/bit.out +++ src/test/regress/expected/bit.out @@ -681,6 +681,19 @@ SELECT overlay(B'0101011100' placing '001' from 20); 0101011100001 (1 row) +-- Popcount +SELECT popcount(B'0101011100'::bit(10)); + popcount +---------- + 5 +(1 row) + +SELECT popcount(B'1111111111'::bit(10)); + popcount +---------- + 10 +(1 row) + -- This table is intentionally left around to exercise pg_dump/pg_upgrade CREATE TABLE bit_defaults( b1 bit(4) DEFAULT '1001', diff --git src/test/regress/expected/strings.out src/test/regress/expected/strings.out index 298b6c48c2..9780639038 100644 --- src/test/regress/expected/strings.out +++ src/test/regress/expected/strings.out @@ -2126,3 +2126,10 @@ SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 5 Th\000o\x02\x03 (1 row) +SET bytea_output TO hex; +SELECT popcount(E'\\xdeadbeef'::bytea); + popcount +---------- + 24 +(1 row) + diff --git src/test/regress/sql/bit.sql src/test/regress/sql/bit.sql index 7681d4ab4d..3cd9fb65d3 100644 --- src/test/regress/sql/bit.sql +++ src/test/regress/sql/bit.sql @@ -207,6 +207,10 @@ SELECT overlay(B'0101011100' placing '101' from 6); SELECT overlay(B'0101011100' placing '001' from 11); SELECT overlay(B'0101011100' placing '001' from 20); +-- Popcount +SELECT popcount(B'0101011100'::bit(10)); +SELECT popcount(B'1111111111'::bit(10)); + -- This table is intentionally left around to exercise pg_dump/pg_upgrade CREATE TABLE bit_defaults( b1 bit(4) DEFAULT '1001', diff --git src/test/regress/sql/strings.sql src/test/regress/sql/strings.sql index ad5221ab6b..a35e993ab0 100644 --- src/test/regress/sql/strings.sql +++ src/test/regress/sql/strings.sql @@ -717,3 +717,6 @@ SELECT btrim(E'\\000trim\\000'::bytea, ''::bytea); SELECT encode(overlay(E'Th\\000omas'::bytea placing E'Th\\001omas'::bytea from 2),'escape'); SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 8),'escape'); SELECT encode(overlay(E'Th\\000omas'::bytea placing E'\\002\\003'::bytea from 5 for 3),'escape'); + +SET bytea_output TO hex; +SELECT popcount(E'\\xdeadbeef'::bytea); ^ permalink raw reply [nested|flat] 4+ messages in thread
* [PATCH v2 3/4] psql: bump minimum supported version to v10 @ 2026-04-17 18:34 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 4+ messages in thread From: Nathan Bossart @ 2026-04-17 18:34 UTC (permalink / raw) --- doc/src/sgml/ref/psql-ref.sgml | 2 +- src/bin/psql/command.c | 23 +-- src/bin/psql/describe.c | 255 +-------------------------------- 3 files changed, 11 insertions(+), 269 deletions(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 7c05afd4719..56c2692e618 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -5523,7 +5523,7 @@ PSQL_EDITOR_LINENUMBER_ARG='--line ' or an older major version. Backslash commands are particularly likely to fail if the server is of a newer version than <application>psql</application> itself. However, backslash commands of the <literal>\d</literal> family should - work with servers of versions back to 9.2, though not necessarily with + work with servers of versions back to 10, though not necessarily with servers newer than <application>psql</application> itself. The general functionality of running SQL commands and displaying query results should also work with servers of a newer major version, but this cannot diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 493400f9090..c9573d4b765 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -4471,10 +4471,10 @@ connection_warnings(bool in_startup) /* * Warn if server's major version is newer than ours, or if server - * predates our support cutoff (currently 9.2). + * predates our support cutoff (currently 10). */ if (pset.sversion / 100 > client_ver / 100 || - pset.sversion < 90200) + pset.sversion < 100000) printf(_("WARNING: %s major version %s, server major version %s.\n" " Some psql features might not work.\n"), pset.progname, @@ -6272,15 +6272,13 @@ get_create_object_cmd(EditableObjectType obj_type, Oid oid, * ensure the right view gets replaced. Also, check relation kind * to be sure it's a view. * - * Starting with PG 9.4, views may have WITH [LOCAL|CASCADED] + * Views may have WITH [LOCAL|CASCADED] * CHECK OPTION. These are not part of the view definition * returned by pg_get_viewdef() and so need to be retrieved - * separately. Materialized views (introduced in 9.3) may have + * separately. Materialized views may have * arbitrary storage parameter reloptions. */ printfPQExpBuffer(query, "/* %s */\n", _("Get view's definition and details")); - if (pset.sversion >= 90400) - { appendPQExpBuffer(query, "SELECT nspname, relname, relkind, " "pg_catalog.pg_get_viewdef(c.oid, true), " @@ -6291,19 +6289,6 @@ get_create_object_cmd(EditableObjectType obj_type, Oid oid, "LEFT JOIN pg_catalog.pg_namespace n " "ON c.relnamespace = n.oid WHERE c.oid = %u", oid); - } - else - { - appendPQExpBuffer(query, - "SELECT nspname, relname, relkind, " - "pg_catalog.pg_get_viewdef(c.oid, true), " - "c.reloptions AS reloptions, " - "NULL AS checkoption " - "FROM pg_catalog.pg_class c " - "LEFT JOIN pg_catalog.pg_namespace n " - "ON c.relnamespace = n.oid WHERE c.oid = %u", - oid); - } break; } diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index e1449654f96..76d299fb55c 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3,9 +3,9 @@ * * Support for the various \d ("describe") commands. Note that the current * expectation is that all functions in this file will succeed when working - * with servers of versions 9.2 and up. It's okay to omit irrelevant + * with servers of versions 10 and up. It's okay to omit irrelevant * information for an old server, but not to fail outright. (But failing - * against a pre-9.2 server is allowed.) + * against a pre-10 server is allowed.) * * Copyright (c) 2000-2026, PostgreSQL Global Development Group * @@ -98,20 +98,12 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem) gettext_noop("Result data type"), gettext_noop("Argument data types")); - if (pset.sversion >= 110000) appendPQExpBuffer(&buf, " pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"\n" "FROM pg_catalog.pg_proc p\n" " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n" "WHERE p.prokind = " CppAsString2(PROKIND_AGGREGATE) "\n", gettext_noop("Description")); - else - appendPQExpBuffer(&buf, - " pg_catalog.obj_description(p.oid, 'pg_proc') as \"%s\"\n" - "FROM pg_catalog.pg_proc p\n" - " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n" - "WHERE p.proisagg\n", - gettext_noop("Description")); if (!showSystem && !pattern) appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n" @@ -154,16 +146,6 @@ describeAccessMethods(const char *pattern, bool verbose) printQueryOpt myopt = pset.popt; static const bool translate_columns[] = {false, true, false, false}; - if (pset.sversion < 90600) - { - char sverbuf[32]; - - pg_log_error("The server (version %s) does not support access methods.", - formatPGVersionNumber(pset.sversion, false, - sverbuf, sizeof(sverbuf))); - return true; - } - initPQExpBuffer(&buf); printfPQExpBuffer(&buf, "/* %s */\n", _("Get matching access methods")); @@ -312,9 +294,6 @@ describeFunctions(const char *functypes, const char *func_pattern, printQueryOpt myopt = pset.popt; static const bool translate_columns[] = {false, false, false, false, true, true, true, false, true, true, false, false, false, false}; - /* No "Parallel" column before 9.6 */ - static const bool translate_columns_pre_96[] = {false, false, false, false, true, true, false, true, true, false, false, false, false}; - if (strlen(functypes) != strspn(functypes, df_options)) { pg_log_error("\\df only takes [%s] as options", df_options); @@ -400,7 +379,6 @@ describeFunctions(const char *functypes, const char *func_pattern, gettext_noop("stable"), gettext_noop("volatile"), gettext_noop("Volatility")); - if (pset.sversion >= 90600) appendPQExpBuffer(&buf, ",\n CASE\n" " WHEN p.proparallel = " @@ -613,16 +591,8 @@ describeFunctions(const char *functypes, const char *func_pattern, myopt.title = _("List of functions"); myopt.translate_header = true; - if (pset.sversion >= 90600) - { myopt.translate_columns = translate_columns; myopt.n_translate_columns = lengthof(translate_columns); - } - else - { - myopt.translate_columns = translate_columns_pre_96; - myopt.n_translate_columns = lengthof(translate_columns_pre_96); - } printQuery(res, &myopt, pset.queryFout, false, pset.logfile); @@ -1108,38 +1078,6 @@ permissionsList(const char *pattern, bool showSystem) " ), E'\\n') AS \"%s\"", gettext_noop("Column privileges")); - if (pset.sversion >= 90500 && pset.sversion < 100000) - appendPQExpBuffer(&buf, - ",\n pg_catalog.array_to_string(ARRAY(\n" - " SELECT polname\n" - " || CASE WHEN polcmd != '*' THEN\n" - " E' (' || polcmd::pg_catalog.text || E'):'\n" - " ELSE E':'\n" - " END\n" - " || CASE WHEN polqual IS NOT NULL THEN\n" - " E'\\n (u): ' || pg_catalog.pg_get_expr(polqual, polrelid)\n" - " ELSE E''\n" - " END\n" - " || CASE WHEN polwithcheck IS NOT NULL THEN\n" - " E'\\n (c): ' || pg_catalog.pg_get_expr(polwithcheck, polrelid)\n" - " ELSE E''\n" - " END" - " || CASE WHEN polroles <> '{0}' THEN\n" - " E'\\n to: ' || pg_catalog.array_to_string(\n" - " ARRAY(\n" - " SELECT rolname\n" - " FROM pg_catalog.pg_roles\n" - " WHERE oid = ANY (polroles)\n" - " ORDER BY 1\n" - " ), E', ')\n" - " ELSE E''\n" - " END\n" - " FROM pg_catalog.pg_policy pol\n" - " WHERE polrelid = c.oid), E'\\n')\n" - " AS \"%s\"", - gettext_noop("Policies")); - - if (pset.sversion >= 100000) appendPQExpBuffer(&buf, ",\n pg_catalog.array_to_string(ARRAY(\n" " SELECT polname\n" @@ -1666,7 +1604,7 @@ describeOneTableDetails(const char *schemaname, : "''"), oid); } - else if (pset.sversion >= 100000) + else { appendPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " @@ -1683,57 +1621,6 @@ describeOneTableDetails(const char *schemaname, : "''"), oid); } - else if (pset.sversion >= 90500) - { - appendPQExpBuffer(&buf, - "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " - "c.relhastriggers, c.relrowsecurity, c.relforcerowsecurity, " - "c.relhasoids, false as relispartition, %s, c.reltablespace, " - "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " - "c.relpersistence, c.relreplident\n" - "FROM pg_catalog.pg_class c\n " - "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n" - "WHERE c.oid = '%s';", - (verbose ? - "pg_catalog.array_to_string(c.reloptions || " - "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n" - : "''"), - oid); - } - else if (pset.sversion >= 90400) - { - appendPQExpBuffer(&buf, - "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " - "c.relhastriggers, false, false, c.relhasoids, " - "false as relispartition, %s, c.reltablespace, " - "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " - "c.relpersistence, c.relreplident\n" - "FROM pg_catalog.pg_class c\n " - "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n" - "WHERE c.oid = '%s';", - (verbose ? - "pg_catalog.array_to_string(c.reloptions || " - "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n" - : "''"), - oid); - } - else - { - appendPQExpBuffer(&buf, - "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " - "c.relhastriggers, false, false, c.relhasoids, " - "false as relispartition, %s, c.reltablespace, " - "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " - "c.relpersistence\n" - "FROM pg_catalog.pg_class c\n " - "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n" - "WHERE c.oid = '%s';", - (verbose ? - "pg_catalog.array_to_string(c.reloptions || " - "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n" - : "''"), - oid); - } res = PSQLexec(buf.data); if (!res) @@ -1761,8 +1648,7 @@ describeOneTableDetails(const char *schemaname, tableinfo.reloftype = (strcmp(PQgetvalue(res, 0, 11), "") != 0) ? pg_strdup(PQgetvalue(res, 0, 11)) : NULL; tableinfo.relpersistence = *(PQgetvalue(res, 0, 12)); - tableinfo.relreplident = (pset.sversion >= 90400) ? - *(PQgetvalue(res, 0, 13)) : 'd'; + tableinfo.relreplident = *(PQgetvalue(res, 0, 13)); if (pset.sversion >= 120000) tableinfo.relam = PQgetisnull(res, 0, 14) ? NULL : pg_strdup(PQgetvalue(res, 0, 14)); @@ -1781,8 +1667,6 @@ describeOneTableDetails(const char *schemaname, char *footers[3] = {NULL, NULL, NULL}; printfPQExpBuffer(&buf, "/* %s */\n", _("Get sequence information")); - if (pset.sversion >= 100000) - { appendPQExpBuffer(&buf, "SELECT pg_catalog.format_type(seqtypid, NULL) AS \"%s\",\n" " seqstart AS \"%s\",\n" @@ -1804,30 +1688,6 @@ describeOneTableDetails(const char *schemaname, "FROM pg_catalog.pg_sequence\n" "WHERE seqrelid = '%s';", oid); - } - else - { - appendPQExpBuffer(&buf, - "SELECT 'bigint' AS \"%s\",\n" - " start_value AS \"%s\",\n" - " min_value AS \"%s\",\n" - " max_value AS \"%s\",\n" - " increment_by AS \"%s\",\n" - " CASE WHEN is_cycled THEN '%s' ELSE '%s' END AS \"%s\",\n" - " cache_value AS \"%s\"\n", - gettext_noop("Type"), - gettext_noop("Start"), - gettext_noop("Minimum"), - gettext_noop("Maximum"), - gettext_noop("Increment"), - gettext_noop("yes"), - gettext_noop("no"), - gettext_noop("Cycles?"), - gettext_noop("Cache")); - appendPQExpBuffer(&buf, "FROM %s", fmtId(schemaname)); - /* must be separate because fmtId isn't reentrant */ - appendPQExpBuffer(&buf, ".%s;", fmtId(relationname)); - } res = PSQLexec(buf.data); if (!res) @@ -2045,10 +1905,7 @@ describeOneTableDetails(const char *schemaname, appendPQExpBufferStr(&buf, ",\n (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type t\n" " WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation) AS attcollation"); attcoll_col = cols++; - if (pset.sversion >= 100000) appendPQExpBufferStr(&buf, ",\n a.attidentity"); - else - appendPQExpBufferStr(&buf, ",\n ''::pg_catalog.char AS attidentity"); attidentity_col = cols++; if (pset.sversion >= 120000) appendPQExpBufferStr(&buf, ",\n a.attgenerated"); @@ -2059,14 +1916,11 @@ describeOneTableDetails(const char *schemaname, if (tableinfo.relkind == RELKIND_INDEX || tableinfo.relkind == RELKIND_PARTITIONED_INDEX) { - if (pset.sversion >= 110000) - { appendPQExpBuffer(&buf, ",\n CASE WHEN a.attnum <= (SELECT i.indnkeyatts FROM pg_catalog.pg_index i WHERE i.indexrelid = '%s') THEN '%s' ELSE '%s' END AS is_key", oid, gettext_noop("yes"), gettext_noop("no")); isindexkey_col = cols++; - } appendPQExpBufferStr(&buf, ",\n pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef"); indexdef_col = cols++; } @@ -2461,10 +2315,7 @@ describeOneTableDetails(const char *schemaname, CppAsString2(CONSTRAINT_EXCLUSION) ") AND " "condeferred) AS condeferred,\n"); - if (pset.sversion >= 90400) appendPQExpBufferStr(&buf, "i.indisreplident,\n"); - else - appendPQExpBufferStr(&buf, "false AS indisreplident,\n"); if (pset.sversion >= 150000) appendPQExpBufferStr(&buf, "i.indnullsnotdistinct,\n"); @@ -2569,10 +2420,7 @@ describeOneTableDetails(const char *schemaname, "pg_catalog.pg_get_indexdef(i.indexrelid, 0, true),\n " "pg_catalog.pg_get_constraintdef(con.oid, true), " "contype, condeferrable, condeferred"); - if (pset.sversion >= 90400) appendPQExpBufferStr(&buf, ", i.indisreplident"); - else - appendPQExpBufferStr(&buf, ", false AS indisreplident"); appendPQExpBufferStr(&buf, ", c2.reltablespace"); if (pset.sversion >= 180000) appendPQExpBufferStr(&buf, ", con.conperiod"); @@ -2823,17 +2671,11 @@ describeOneTableDetails(const char *schemaname, PQclear(result); /* print any row-level policies */ - if (pset.sversion >= 90500) - { printfPQExpBuffer(&buf, "/* %s */\n", _("Get row-level policies for this table")); appendPQExpBufferStr(&buf, "SELECT pol.polname,"); - if (pset.sversion >= 100000) appendPQExpBufferStr(&buf, " pol.polpermissive,\n"); - else - appendPQExpBufferStr(&buf, - " 't' as polpermissive,\n"); appendPQExpBuffer(&buf, " CASE WHEN pol.polroles = '{0}' THEN NULL ELSE pg_catalog.array_to_string(array(select rolname from pg_catalog.pg_roles where oid = any (pol.polroles) order by 1),',') END,\n" " pg_catalog.pg_get_expr(pol.polqual, pol.polrelid),\n" @@ -2904,7 +2746,6 @@ describeOneTableDetails(const char *schemaname, printTableAddFooter(&cont, buf.data); } PQclear(result); - } /* print any extended statistics */ if (pset.sversion >= 140000) @@ -3007,7 +2848,7 @@ describeOneTableDetails(const char *schemaname, } PQclear(result); } - else if (pset.sversion >= 100000) + else { printfPQExpBuffer(&buf, "/* %s */\n", _("Get extended statistics for this table")); @@ -3173,8 +3014,6 @@ describeOneTableDetails(const char *schemaname, } /* print any publications */ - if (pset.sversion >= 100000) - { printfPQExpBuffer(&buf, "/* %s */\n", _("Get publications that publish this table")); if (pset.sversion >= 150000) @@ -3284,7 +3123,6 @@ describeOneTableDetails(const char *schemaname, printTableAddFooter(&cont, buf.data); } PQclear(result); - } /* Print publications where the table is in the EXCEPT clause */ if (pset.sversion >= 190000) @@ -3706,7 +3544,7 @@ describeOneTableDetails(const char *schemaname, "ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT'," " c.oid::pg_catalog.regclass::pg_catalog.text;", oid); - else if (pset.sversion >= 100000) + else appendPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass, c.relkind," " false AS inhdetachpending," @@ -3716,14 +3554,6 @@ describeOneTableDetails(const char *schemaname, "ORDER BY pg_catalog.pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT'," " c.oid::pg_catalog.regclass::pg_catalog.text;", oid); - else - appendPQExpBuffer(&buf, - "SELECT c.oid::pg_catalog.regclass, c.relkind," - " false AS inhdetachpending, NULL\n" - "FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i\n" - "WHERE c.oid = i.inhrelid AND i.inhparent = '%s'\n" - "ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", - oid); result = PSQLexec(buf.data); if (!result) @@ -3964,11 +3794,7 @@ describeRoles(const char *pattern, bool verbose, bool showSystem) ncols++; } appendPQExpBufferStr(&buf, "\n, r.rolreplication"); - - if (pset.sversion >= 90500) - { appendPQExpBufferStr(&buf, "\n, r.rolbypassrls"); - } appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_roles r\n"); @@ -4023,7 +3849,6 @@ describeRoles(const char *pattern, bool verbose, bool showSystem) if (strcmp(PQgetvalue(res, i, (verbose ? 9 : 8)), "t") == 0) add_role_attribute(&buf, _("Replication")); - if (pset.sversion >= 90500) if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0) add_role_attribute(&buf, _("Bypass RLS")); @@ -4514,19 +4339,6 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose) const char *tabletitle; bool mixed_output = false; - /* - * Note: Declarative table partitioning is only supported as of Pg 10.0. - */ - if (pset.sversion < 100000) - { - char sverbuf[32]; - - pg_log_error("The server (version %s) does not support declarative table partitioning.", - formatPGVersionNumber(pset.sversion, false, - sverbuf, sizeof(sverbuf))); - return true; - } - /* If no relation kind was selected, show them all */ if (!showTables && !showIndexes) showTables = showIndexes = true; @@ -5034,16 +4846,6 @@ listEventTriggers(const char *pattern, bool verbose) static const bool translate_columns[] = {false, false, false, true, false, false, false}; - if (pset.sversion < 90300) - { - char sverbuf[32]; - - pg_log_error("The server (version %s) does not support event triggers.", - formatPGVersionNumber(pset.sversion, false, - sverbuf, sizeof(sverbuf))); - return true; - } - initPQExpBuffer(&buf); printfPQExpBuffer(&buf, "/* %s */\n", _("Get matching event triggers")); @@ -5113,16 +4915,6 @@ listExtendedStats(const char *pattern, bool verbose) PGresult *res; printQueryOpt myopt = pset.popt; - if (pset.sversion < 100000) - { - char sverbuf[32]; - - pg_log_error("The server (version %s) does not support extended statistics.", - formatPGVersionNumber(pset.sversion, false, - sverbuf, sizeof(sverbuf))); - return true; - } - initPQExpBuffer(&buf); printfPQExpBuffer(&buf, "/* %s */\n", _("Get matching extended statistics")); @@ -5352,7 +5144,6 @@ listCollations(const char *pattern, bool verbose, bool showSystem) gettext_noop("Schema"), gettext_noop("Name")); - if (pset.sversion >= 100000) appendPQExpBuffer(&buf, " CASE c.collprovider " "WHEN " CppAsString2(COLLPROVIDER_DEFAULT) " THEN 'default' " @@ -5361,10 +5152,6 @@ listCollations(const char *pattern, bool verbose, bool showSystem) "WHEN " CppAsString2(COLLPROVIDER_ICU) " THEN 'icu' " "END AS \"%s\",\n", gettext_noop("Provider")); - else - appendPQExpBuffer(&buf, - " 'libc' AS \"%s\",\n", - gettext_noop("Provider")); appendPQExpBuffer(&buf, " c.collcollate AS \"%s\",\n" @@ -6688,16 +6475,6 @@ listPublications(const char *pattern) printQueryOpt myopt = pset.popt; static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false, false}; - if (pset.sversion < 100000) - { - char sverbuf[32]; - - pg_log_error("The server (version %s) does not support publications.", - formatPGVersionNumber(pset.sversion, false, - sverbuf, sizeof(sverbuf))); - return true; - } - initPQExpBuffer(&buf); printfPQExpBuffer(&buf, "/* %s */\n", _("Get matching publications")); @@ -6835,16 +6612,6 @@ describePublications(const char *pattern) PQExpBufferData title; printTableContent cont; - if (pset.sversion < 100000) - { - char sverbuf[32]; - - pg_log_error("The server (version %s) does not support publications.", - formatPGVersionNumber(pset.sversion, false, - sverbuf, sizeof(sverbuf))); - return true; - } - has_pubsequence = (pset.sversion >= 190000); has_pubtruncate = (pset.sversion >= 110000); has_pubgencols = (pset.sversion >= 180000); @@ -7095,16 +6862,6 @@ describeSubscriptions(const char *pattern, bool verbose) false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; - if (pset.sversion < 100000) - { - char sverbuf[32]; - - pg_log_error("The server (version %s) does not support subscriptions.", - formatPGVersionNumber(pset.sversion, false, - sverbuf, sizeof(sverbuf))); - return true; - } - initPQExpBuffer(&buf); printfPQExpBuffer(&buf, "/* %s */\n", _("Get matching subscriptions")); -- 2.50.1 (Apple Git-155) --CT89ko5pLUrsvFtH Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0004-run-pgindent.patch ^ permalink raw reply [nested|flat] 4+ messages in thread
end of thread, other threads:[~2026-04-17 18:34 UTC | newest] Thread overview: 4+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2020-12-30 10:51 [PATCH v1] popcount David Fetter <david@fetter.org> 2020-12-30 10:51 [PATCH v5] popcount David Fetter <david@fetter.org> 2020-12-30 10:51 [PATCH v2] popcount David Fetter <david@fetter.org> 2026-04-17 18:34 [PATCH v2 3/4] psql: bump minimum supported version to v10 Nathan Bossart <nathan@postgresql.org>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox