agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH v9] Generated columns 114+ messages / 3 participants [nested] [flat]
* [PATCH v9] Generated columns @ 2019-03-17 15:58 Peter Eisentraut <peter@eisentraut.org> 0 siblings, 0 replies; 114+ messages in thread From: Peter Eisentraut @ 2019-03-17 15:58 UTC (permalink / raw) This is an SQL-standard feature that allows creating columns that are computed from expressions rather than assigned, similar to a view or materialized view but on a column basis. This implement one kind of generated column: stored (computed on write). Another kind, virtual (computed on read), is planned for the future, and some room is left for it. Discussion: https://www.postgresql.org/message-id/flat/b151f851-4019-bdb1-699e-ebab07d2f40a@2ndquadrant.com --- .../postgres_fdw/expected/postgres_fdw.out | 25 + contrib/postgres_fdw/postgres_fdw.c | 3 +- contrib/postgres_fdw/sql/postgres_fdw.sql | 14 + doc/src/sgml/catalogs.sgml | 19 +- doc/src/sgml/ddl.sgml | 118 +++ doc/src/sgml/information_schema.sgml | 66 +- doc/src/sgml/protocol.sgml | 4 +- doc/src/sgml/ref/copy.sgml | 3 +- doc/src/sgml/ref/create_foreign_table.sgml | 27 +- doc/src/sgml/ref/create_table.sgml | 45 +- doc/src/sgml/ref/create_trigger.sgml | 4 +- doc/src/sgml/textsearch.sgml | 26 +- doc/src/sgml/trigger.sgml | 18 + src/backend/access/common/tupdesc.c | 11 + src/backend/catalog/heap.c | 92 ++- src/backend/catalog/information_schema.sql | 30 +- src/backend/commands/copy.c | 31 +- src/backend/commands/tablecmds.c | 167 +++- src/backend/commands/trigger.c | 31 +- src/backend/commands/typecmds.c | 6 +- src/backend/executor/execMain.c | 8 +- src/backend/executor/execReplication.c | 11 + src/backend/executor/nodeModifyTable.c | 112 +++ src/backend/nodes/copyfuncs.c | 2 + src/backend/nodes/equalfuncs.c | 2 + src/backend/nodes/outfuncs.c | 9 + src/backend/nodes/readfuncs.c | 1 + src/backend/optimizer/plan/createplan.c | 8 +- src/backend/optimizer/util/inherit.c | 6 + src/backend/optimizer/util/plancat.c | 19 + src/backend/parser/analyze.c | 27 + src/backend/parser/gram.y | 14 +- src/backend/parser/parse_agg.c | 11 + src/backend/parser/parse_expr.c | 5 + src/backend/parser/parse_func.c | 3 + src/backend/parser/parse_relation.c | 19 + src/backend/parser/parse_utilcmd.c | 66 +- src/backend/replication/logical/proto.c | 9 +- src/backend/replication/logical/relation.c | 2 +- src/backend/replication/logical/tablesync.c | 6 +- src/backend/replication/logical/worker.c | 2 +- src/backend/replication/pgoutput/pgoutput.c | 2 +- src/backend/rewrite/rewriteHandler.c | 36 +- src/backend/utils/cache/lsyscache.c | 33 + src/backend/utils/cache/partcache.c | 1 + src/backend/utils/cache/relcache.c | 4 + src/bin/pg_dump/pg_dump.c | 40 +- src/bin/pg_dump/pg_dump.h | 1 + src/bin/pg_dump/pg_dump_sort.c | 10 + src/bin/pg_dump/t/002_pg_dump.pl | 17 + src/bin/psql/describe.c | 23 +- src/include/access/tupdesc.h | 1 + src/include/catalog/heap.h | 4 +- src/include/catalog/pg_attribute.h | 5 + src/include/catalog/pg_class.dat | 2 +- src/include/executor/nodeModifyTable.h | 2 + src/include/nodes/execnodes.h | 3 + src/include/nodes/parsenodes.h | 24 +- src/include/optimizer/plancat.h | 2 + src/include/parser/kwlist.h | 1 + src/include/parser/parse_node.h | 3 +- src/include/utils/lsyscache.h | 1 + src/pl/plperl/expected/plperl_trigger.out | 95 +++ src/pl/plperl/plperl.c | 40 +- src/pl/plperl/sql/plperl_trigger.sql | 36 + src/pl/plpgsql/src/pl_exec.c | 20 + src/pl/plpython/expected/plpython_trigger.out | 94 +++ src/pl/plpython/plpy_cursorobject.c | 5 +- src/pl/plpython/plpy_exec.c | 23 +- src/pl/plpython/plpy_spi.c | 3 +- src/pl/plpython/plpy_typeio.c | 17 +- src/pl/plpython/plpy_typeio.h | 2 +- src/pl/plpython/sql/plpython_trigger.sql | 37 + src/pl/tcl/expected/pltcl_trigger.out | 99 +++ src/pl/tcl/pltcl.c | 50 +- src/pl/tcl/sql/pltcl_trigger.sql | 36 + .../regress/expected/create_table_like.out | 46 ++ src/test/regress/expected/generated.out | 739 ++++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 1 + src/test/regress/sql/create_table_like.sql | 14 + src/test/regress/sql/generated.sql | 436 +++++++++++ src/test/subscription/t/011_generated.pl | 65 ++ 83 files changed, 3008 insertions(+), 149 deletions(-) create mode 100644 src/test/regress/expected/generated.out create mode 100644 src/test/regress/sql/generated.sql create mode 100644 src/test/subscription/t/011_generated.pl diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 42108bd3d4..6c73b492fd 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -6431,6 +6431,31 @@ select * from rem1; 11 | bye remote (4 rows) +-- =================================================================== +-- test generated columns +-- =================================================================== +create table gloc1 (a int, b int); +alter table gloc1 set (autovacuum_enabled = 'false'); +create foreign table grem1 ( + a int, + b int generated always as (a * 2) stored) + server loopback options(table_name 'gloc1'); +insert into grem1 (a) values (1), (2); +update grem1 set a = 22 where a = 2; +select * from gloc1; + a | b +----+---- + 1 | 2 + 22 | 44 +(2 rows) + +select * from grem1; + a | b +----+---- + 1 | 2 + 22 | 44 +(2 rows) + -- =================================================================== -- test local triggers -- =================================================================== diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 2f387fac42..d0d36aaa0d 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -1644,9 +1644,10 @@ postgresPlanForeignModify(PlannerInfo *root, else if (operation == CMD_UPDATE) { int col; + Bitmapset *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols); col = -1; - while ((col = bms_next_member(rte->updatedCols, col)) >= 0) + while ((col = bms_next_member(allUpdatedCols, col)) >= 0) { /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */ AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber; diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index eb9d1ad59d..e54c9f06cd 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -1363,6 +1363,20 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl select * from loc1; select * from rem1; +-- =================================================================== +-- test generated columns +-- =================================================================== +create table gloc1 (a int, b int); +alter table gloc1 set (autovacuum_enabled = 'false'); +create foreign table grem1 ( + a int, + b int generated always as (a * 2) stored) + server loopback options(table_name 'gloc1'); +insert into grem1 (a) values (1), (2); +update grem1 set a = 22 where a = 2; +select * from gloc1; +select * from grem1; + -- =================================================================== -- test local triggers -- =================================================================== diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 0fd792ff1a..095233a171 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1129,9 +1129,11 @@ <title><structname>pg_attribute</structname> Columns</title> <entry><type>bool</type></entry> <entry></entry> <entry> - This column has a default value, in which case there will be a - corresponding entry in the <structname>pg_attrdef</structname> - catalog that actually defines the value. + This column has a default expression or generation expression, in which + case there will be a corresponding entry in the + <structname>pg_attrdef</structname> catalog that actually defines the + expression. (Check <structfield>attgenerated</structfield> to + determine whether this is a default or a generation expression.) </entry> </row> @@ -1159,6 +1161,17 @@ <title><structname>pg_attribute</structname> Columns</title> </entry> </row> + <row> + <entry><structfield>attgenerated</structfield></entry> + <entry><type>char</type></entry> + <entry></entry> + <entry> + If a zero byte (<literal>''</literal>), then not a generated column. + Otherwise, <literal>s</literal> = stored. (Other values might be added + in the future.) + </entry> + </row> + <row> <entry><structfield>attisdropped</structfield></entry> <entry><type>bool</type></entry> diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 110f6b4657..1fe27c5da9 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -233,6 +233,124 @@ <title>Default Values</title> </para> </sect1> + <sect1 id="ddl-generated-columns"> + <title>Generated Columns</title> + + <indexterm zone="ddl-generated-columns"> + <primary>generated column</primary> + </indexterm> + + <para> + A generated column is a special column that is always computed from other + columns. Thus, it is for columns what a view is for tables. There are two + kinds of generated columns: stored and virtual. A stored generated column + is computed when it is written (inserted or updated) and occupies storage + as if it were a normal column. A virtual generated column occupies no + storage and is computed when it is read. Thus, a virtual generated column + is similar to a view and a stored generated column is similar to a + materialized view (except that it is always updated automatically). + PostgreSQL currently implements only stored generated columns. + </para> + + <para> + To create a generated column, use the <literal>GENERATED ALWAYS + AS</literal> clause in <command>CREATE TABLE</command>, for example: +<programlisting> +CREATE TABLE people ( + ..., + height_cm numeric, + height_in numeric <emphasis>GENERATED ALWAYS AS (height_cm * 2.54) STORED</emphasis> +); +</programlisting> + The keyword <literal>STORED</literal> must be specified to choose the + stored kind of generated column. See <xref linkend="sql-createtable"/> for + more details. + </para> + + <para> + A generated column cannot be written to directly. In + <command>INSERT</command> or <command>UPDATE</command> commands, a value + cannot be specified for a generated column, but the keyword + <literal>DEFAULT</literal> may be specified. + </para> + + <para> + Consider the differences between a column with a default and a generated + column. The column default is evaluated once when the row is first + inserted if no other value was provided; a generated column is updated + whenever the row changes and cannot be overridden. A column default may + not refer to other columns of the table; a generation expression would + normally do so. A column default can use volatile functions, for example + <literal>random()</literal> or functions referring to the current time; + this is not allowed for generated columns. + </para> + + <para> + Several restrictions apply to the definition of generated columns and + tables involving generated columns: + + <itemizedlist> + <listitem> + <para> + The generation expression can only use immutable functions and cannot + use subqueries or reference anything other than the current row in any + way. + </para> + </listitem> + <listitem> + <para> + A generation expression cannot reference another generated column. + </para> + </listitem> + <listitem> + <para> + A generation expression cannot reference a system column, except + <varname>tableoid</varname>. + </para> + </listitem> + <listitem> + <para> + A generated column cannot have a column default or an identity definition. + </para> + </listitem> + <listitem> + <para> + A generated column cannot be part of a partition key. + </para> + </listitem> + <listitem> + <para> + Foreign tables can have generated columns. See <xref + linkend="sql-createforeigntable"/> for details. + </para> + </listitem> + </itemizedlist> + </para> + + <para> + Additional considerations apply to the use of generated columns. + <itemizedlist> + <listitem> + <para> + Generated columns maintain access privileges separately from their + underlying base columns. So, it is possible to arrange it so that a + particular role can read from a generated column but not from the + underlying base columns. + </para> + </listitem> + <listitem> + <para> + Generated columns are, conceptually, updated after + <literal>BEFORE</literal> triggers have run. Therefore, changes made to + base columns in a <literal>BEFORE</literal> trigger will be reflected in + generated columns. But conversely, it is not allowed to access + generated columns in <literal>BEFORE</literal> triggers. + </para> + </listitem> + </itemizedlist> + </para> + </sect1> + <sect1 id="ddl-constraints"> <title>Constraints</title> diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml index b13700da92..1321ade44a 100644 --- a/doc/src/sgml/information_schema.sgml +++ b/doc/src/sgml/information_schema.sgml @@ -952,6 +952,62 @@ <title><literal>collation_character_set_applicability</literal> Columns</title> </table> </sect1> + <sect1 id="infoschema-column-column-usage"> + <title><literal>column_column_usage</literal></title> + + <para> + The view <literal>column_column_usage</literal> identifies all generated + columns that depend on another base column in the same table. Only tables + owned by a currently enabled role are included. + </para> + + <table> + <title><literal>column_column_usage</literal> Columns</title> + + <tgroup cols="3"> + <thead> + <row> + <entry>Name</entry> + <entry>Data Type</entry> + <entry>Description</entry> + </row> + </thead> + + <tbody> + <row> + <entry><literal>table_catalog</literal></entry> + <entry><type>sql_identifier</type></entry> + <entry>Name of the database containing the table (always the current database)</entry> + </row> + + <row> + <entry><literal>table_schema</literal></entry> + <entry><type>sql_identifier</type></entry> + <entry>Name of the schema containing the table</entry> + </row> + + <row> + <entry><literal>table_name</literal></entry> + <entry><type>sql_identifier</type></entry> + <entry>Name of the table</entry> + </row> + + <row> + <entry><literal>column_name</literal></entry> + <entry><type>sql_identifier</type></entry> + <entry>Name of the base column that a generated column depends on</entry> + </row> + + <row> + <entry><literal>dependent_column</literal></entry> + <entry><type>sql_identifier</type></entry> + <entry>Name of the generated column</entry> + </row> + </tbody> + </tgroup> + </table> + </sect1> + <sect1 id="infoschema-column-domain-usage"> <title><literal>column_domain_usage</literal></title> @@ -1648,13 +1704,19 @@ <title><literal>columns</literal> Columns</title> <row> <entry><literal>is_generated</literal></entry> <entry><type>character_data</type></entry> - <entry>Applies to a feature not available in <productname>PostgreSQL</productname></entry> + <entry> + If the column is a generated column, then <literal>ALWAYS</literal>, + else <literal>NEVER</literal>. + </entry> </row> <row> <entry><literal>generation_expression</literal></entry> <entry><type>character_data</type></entry> - <entry>Applies to a feature not available in <productname>PostgreSQL</productname></entry> + <entry> + If the column is a generated column, then the generation expression, + else null. + </entry> </row> <row> diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index d66b860cbd..a0e1f78bfc 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -6450,7 +6450,7 @@ <title>Logical Replication Message Formats</title> </listitem> </varlistentry> </variablelist> - Next, the following message part appears for each column: + Next, the following message part appears for each column (except generated columns): <variablelist> <varlistentry> <term> @@ -6875,7 +6875,7 @@ <title>Logical Replication Message Formats</title> </listitem> </varlistentry> </variablelist> - Next, one of the following submessages appears for each column: + Next, one of the following submessages appears for each column (except generated columns): <variablelist> <varlistentry> <term> diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml index 254d3ab8eb..5e2992ddac 100644 --- a/doc/src/sgml/ref/copy.sgml +++ b/doc/src/sgml/ref/copy.sgml @@ -103,7 +103,8 @@ <title>Parameters</title> <listitem> <para> An optional list of columns to be copied. If no column list is - specified, all columns of the table will be copied. + specified, all columns of the table except generated columns will be + copied. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml index 19eb5341e7..65ba3e3d37 100644 --- a/doc/src/sgml/ref/create_foreign_table.sgml +++ b/doc/src/sgml/ref/create_foreign_table.sgml @@ -42,7 +42,8 @@ { NOT NULL | NULL | CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] | - DEFAULT <replaceable>default_expr</replaceable> } + DEFAULT <replaceable>default_expr</replaceable> | + GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED } <phrase>and <replaceable class="parameter">table_constraint</replaceable> is:</phrase> @@ -258,6 +259,30 @@ <title>Parameters</title> </listitem> </varlistentry> + <varlistentry> + <term><literal>GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED</literal><indexterm><primary>generated column</primary></indexterm></term> + <listitem> + <para> + This clause creates the column as a <firstterm>generated + column</firstterm>. The column cannot be written to, and when read it + will be computed from the specified expression. + </para> + + <para> + The keyword <literal>STORED</literal> is required to signify that the + column will be computed on write. (The computed value will be presented + to the foreign-data wrapper for storage and must be returned on + reading.) + </para> + + <para> + The generation expression can refer to other columns in the table, but + not other generated columns. Any functions and operators used must be + immutable. References to other tables are not allowed. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><replaceable class="parameter">server_name</replaceable></term> <listitem> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index e94fe2c3b6..3c8ee54859 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -62,6 +62,7 @@ NULL | CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] | DEFAULT <replaceable>default_expr</replaceable> | + GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED | GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] | UNIQUE <replaceable class="parameter">index_parameters</replaceable> | PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> | @@ -83,7 +84,7 @@ <phrase>and <replaceable class="parameter">like_option</replaceable> is:</phrase> -{ INCLUDING | EXCLUDING } { COMMENTS | CONSTRAINTS | DEFAULTS | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL } +{ INCLUDING | EXCLUDING } { COMMENTS | CONSTRAINTS | DEFAULTS | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL } <phrase>and <replaceable class="parameter">partition_bound_spec</replaceable> is:</phrase> @@ -627,6 +628,16 @@ <title>Parameters</title> </listitem> </varlistentry> + <varlistentry> + <term><literal>INCLUDING GENERATED</literal></term> + <listitem> + <para> + Any generation expressions of copied column definitions will be + copied. By default, new columns will be regular base columns. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><literal>INCLUDING IDENTITY</literal></term> <listitem> @@ -797,6 +808,28 @@ <title>Parameters</title> </listitem> </varlistentry> + <varlistentry> + <term><literal>GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED</literal><indexterm><primary>generated column</primary></indexterm></term> + <listitem> + <para> + This clause creates the column as a <firstterm>generated + column</firstterm>. The column cannot be written to, and when read it + will be computed from the specified expression. + </para> + + <para> + The keyword <literal>STORED</literal> is required to signify that the + column will be computed on write and will be stored on disk. default. + </para> + + <para> + The generation expression can refer to other columns in the table, but + not other generated columns. Any functions and operators used must be + immutable. References to other tables are not allowed. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><literal>GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ]</literal></term> <listitem> @@ -2028,6 +2061,16 @@ <title>Multiple Identity Columns</title> </para> </refsect2> + <refsect2> + <title>Generated Columns</title> + + <para> + The option <literal>STORED</literal> is not standard but is also used by + other SQL implementations. The SQL standard does not specify the storage + of generated columns. + </para> + </refsect2> + <refsect2> <title><literal>LIKE</literal> Clause</title> diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml index 6514ffc6ae..6456105de6 100644 --- a/doc/src/sgml/ref/create_trigger.sgml +++ b/doc/src/sgml/ref/create_trigger.sgml @@ -261,7 +261,9 @@ <title>Parameters</title> UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</replaceable> ... ] </synopsis> The trigger will only fire if at least one of the listed columns - is mentioned as a target of the <command>UPDATE</command> command. + is mentioned as a target of the <command>UPDATE</command> command + or if one of the listed columns is a generated column that depends on a + column that is the target of the <command>UPDATE</command>. </para> <para> diff --git a/doc/src/sgml/textsearch.sgml b/doc/src/sgml/textsearch.sgml index 3281f7cd33..40888a4d20 100644 --- a/doc/src/sgml/textsearch.sgml +++ b/doc/src/sgml/textsearch.sgml @@ -620,15 +620,17 @@ <title>Creating Indexes</title> <para> Another approach is to create a separate <type>tsvector</type> column - to hold the output of <function>to_tsvector</function>. This example is a + to hold the output of <function>to_tsvector</function>. To keep this + column automatically up to date with its source data, use a stored + generated column. This example is a concatenation of <literal>title</literal> and <literal>body</literal>, using <function>coalesce</function> to ensure that one field will still be indexed when the other is <literal>NULL</literal>: <programlisting> -ALTER TABLE pgweb ADD COLUMN textsearchable_index_col tsvector; -UPDATE pgweb SET textsearchable_index_col = - to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,'')); +ALTER TABLE pgweb + ADD COLUMN textsearchable_index_col tsvector + GENERATED ALWAYS AS (to_tsvector('english', coalesce(title, '') || ' ' || coalesce(body, ''))) STORED; </programlisting> Then we create a <acronym>GIN</acronym> index to speed up the search: @@ -648,14 +650,6 @@ <title>Creating Indexes</title> </programlisting> </para> - <para> - When using a separate column to store the <type>tsvector</type> - representation, - it is necessary to create a trigger to keep the <type>tsvector</type> - column current anytime <literal>title</literal> or <literal>body</literal> changes. - <xref linkend="textsearch-update-triggers"/> explains how to do that. - </para> - <para> One advantage of the separate-column approach over an expression index is that it is not necessary to explicitly specify the text search @@ -1857,6 +1851,14 @@ <title>Triggers for Automatic Updates</title> <secondary>for updating a derived tsvector column</secondary> </indexterm> + <note> + <para> + The method described in this section has been obsoleted by the use of + stored generated columns, as described in <xref + linkend="textsearch-tables-index"/>. + </para> + </note> + <para> When using a separate column to store the <type>tsvector</type> representation of your documents, it is necessary to create a trigger to update the diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml index be9c228448..67e1861e06 100644 --- a/doc/src/sgml/trigger.sgml +++ b/doc/src/sgml/trigger.sgml @@ -243,6 +243,24 @@ <title>Overview of Trigger Behavior</title> operation, and so they can return <symbol>NULL</symbol>. </para> + <para> + Some considerations apply for generated + columns.<indexterm><primary>generated column</primary><secondary>in + triggers</secondary></indexterm> Stored generated columns are computed after + <literal>BEFORE</literal> triggers and before <literal>AFTER</literal> + triggers. Therefore, the generated value can be inspected in + <literal>AFTER</literal> triggers. In <literal>BEFORE</literal> triggers, + the <literal>OLD</literal> row contains the old generated value, as one + would expect, but the <literal>NEW</literal> row does not yet contain the + new generated value and should not be accessed. In the C language + interface, the content of the column is undefined at this point; a + higher-level programming language should prevent access to a stored + generated column in the <literal>NEW</literal> row in a + <literal>BEFORE</literal> trigger. Changes to the value of a generated + column in a <literal>BEFORE</literal> trigger are ignored and will be + overwritten. + </para> + <para> If more than one trigger is defined for the same event on the same relation, the triggers will be fired in alphabetical order by diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 0158950a43..6bc4e4c036 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -131,6 +131,7 @@ CreateTupleDescCopy(TupleDesc tupdesc) att->atthasdef = false; att->atthasmissing = false; att->attidentity = '\0'; + att->attgenerated = '\0'; } /* We can copy the tuple type identification, too */ @@ -165,6 +166,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc) TupleConstr *cpy = (TupleConstr *) palloc0(sizeof(TupleConstr)); cpy->has_not_null = constr->has_not_null; + cpy->has_generated_stored = constr->has_generated_stored; if ((cpy->num_defval = constr->num_defval) > 0) { @@ -247,6 +249,7 @@ TupleDescCopy(TupleDesc dst, TupleDesc src) att->atthasdef = false; att->atthasmissing = false; att->attidentity = '\0'; + att->attgenerated = '\0'; } dst->constr = NULL; @@ -300,6 +303,7 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno, dstAtt->atthasdef = false; dstAtt->atthasmissing = false; dstAtt->attidentity = '\0'; + dstAtt->attgenerated = '\0'; } /* @@ -456,6 +460,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) return false; if (attr1->attidentity != attr2->attidentity) return false; + if (attr1->attgenerated != attr2->attgenerated) + return false; if (attr1->attisdropped != attr2->attisdropped) return false; if (attr1->attislocal != attr2->attislocal) @@ -476,6 +482,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) return false; if (constr1->has_not_null != constr2->has_not_null) return false; + if (constr1->has_generated_stored != constr2->has_generated_stored) + return false; n = constr1->num_defval; if (n != (int) constr2->num_defval) return false; @@ -638,6 +646,7 @@ TupleDescInitEntry(TupleDesc desc, att->atthasdef = false; att->atthasmissing = false; att->attidentity = '\0'; + att->attgenerated = '\0'; att->attisdropped = false; att->attislocal = true; att->attinhcount = 0; @@ -697,6 +706,7 @@ TupleDescInitBuiltinEntry(TupleDesc desc, att->atthasdef = false; att->atthasmissing = false; att->attidentity = '\0'; + att->attgenerated = '\0'; att->attisdropped = false; att->attislocal = true; att->attinhcount = 0; @@ -853,6 +863,7 @@ BuildDescForRelation(List *schema) TupleConstr *constr = (TupleConstr *) palloc0(sizeof(TupleConstr)); constr->has_not_null = true; + constr->has_generated_stored = false; constr->defval = NULL; constr->missing = NULL; constr->num_defval = 0; diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index c7b5ff62f9..af4d217063 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -70,6 +70,7 @@ #include "parser/parse_collate.h" #include "parser/parse_expr.h" #include "parser/parse_relation.h" +#include "parser/parsetree.h" #include "partitioning/partdesc.h" #include "storage/lmgr.h" #include "storage/predicate.h" @@ -687,6 +688,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef); values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing); values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity); + values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attribute->attgenerated); values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped); values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal); values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount); @@ -2152,6 +2154,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, Relation attrrel; HeapTuple atttup; Form_pg_attribute attStruct; + char attgenerated; Oid attrdefOid; ObjectAddress colobject, defobject; @@ -2199,6 +2202,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, elog(ERROR, "cache lookup failed for attribute %d of relation %u", attnum, RelationGetRelid(rel)); attStruct = (Form_pg_attribute) GETSTRUCT(atttup); + attgenerated = attStruct->attgenerated; if (!attStruct->atthasdef) { Form_pg_attribute defAttStruct; @@ -2219,7 +2223,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, valuesAtt[Anum_pg_attribute_atthasdef - 1] = true; replacesAtt[Anum_pg_attribute_atthasdef - 1] = true; - if (add_column_mode) + if (add_column_mode && !attgenerated) { expr2 = expression_planner(expr2); estate = CreateExecutorState(); @@ -2281,7 +2285,26 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, /* * Record dependencies on objects used in the expression, too. */ - recordDependencyOnExpr(&defobject, expr, NIL, DEPENDENCY_NORMAL); + if (attgenerated) + { + /* + * Generated column: Dropping anything that the generation expression + * refers to automatically drops the generated column. + */ + recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel), + DEPENDENCY_AUTO, + DEPENDENCY_AUTO, false); + } + else + { + /* + * Normal default: Dropping anything that the default refers to + * requires CASCADE and drops the default only. + */ + recordDependencyOnSingleRelExpr(&defobject, expr, RelationGetRelid(rel), + DEPENDENCY_NORMAL, + DEPENDENCY_NORMAL, false); + } /* * Post creation hook for attribute defaults. @@ -2539,12 +2562,14 @@ AddRelationNewConstraints(Relation rel, expr = cookDefault(pstate, colDef->raw_default, atp->atttypid, atp->atttypmod, - NameStr(atp->attname)); + NameStr(atp->attname), + atp->attgenerated); /* * If the expression is just a NULL constant, we do not bother to make * an explicit pg_attrdef entry, since the default behavior is - * equivalent. + * equivalent. This applies to column defaults, but not for generation + * expressions. * * Note a nonobvious property of this test: if the column is of a * domain type, what we'll get is not a bare null Const but a @@ -2553,7 +2578,9 @@ AddRelationNewConstraints(Relation rel, * override any default that the domain might have. */ if (expr == NULL || - (IsA(expr, Const) &&((Const *) expr)->constisnull)) + (!colDef->generated && + IsA(expr, Const) && + castNode(Const, expr)->constisnull)) continue; /* If the DEFAULT is volatile we cannot use a missing value */ @@ -2910,6 +2937,46 @@ SetRelationNumChecks(Relation rel, int numchecks) table_close(relrel, RowExclusiveLock); } +/* + * Check for references to generated columns + */ +static bool +check_nested_generated_walker(Node *node, void *context) +{ + ParseState *pstate = context; + + if (node == NULL) + return false; + else if (IsA(node, Var)) + { + Var *var = (Var *) node; + Oid relid; + AttrNumber attnum; + + relid = rt_fetch(var->varno, pstate->p_rtable)->relid; + attnum = var->varattno; + + if (relid && attnum && get_attgenerated(relid, attnum)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use generated column \"%s\" in column generation expression", + get_attname(relid, attnum, false)), + errdetail("A generated column cannot reference another generated column."), + parser_errposition(pstate, var->location))); + + return false; + } + else + return expression_tree_walker(node, check_nested_generated_walker, + (void *) context); +} + +static void +check_nested_generated(ParseState *pstate, Node *node) +{ + check_nested_generated_walker(node, pstate); +} + /* * Take a raw default and convert it to a cooked format ready for * storage. @@ -2927,7 +2994,8 @@ cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, int32 atttypmod, - const char *attname) + const char *attname, + char attgenerated) { Node *expr; @@ -2936,17 +3004,25 @@ cookDefault(ParseState *pstate, /* * Transform raw parsetree to executable expression. */ - expr = transformExpr(pstate, raw_default, EXPR_KIND_COLUMN_DEFAULT); + expr = transformExpr(pstate, raw_default, attgenerated ? EXPR_KIND_GENERATED_COLUMN : EXPR_KIND_COLUMN_DEFAULT); /* * Make sure default expr does not refer to any vars (we need this check * since the pstate includes the target table). */ - if (contain_var_clause(expr)) + if (!attgenerated && contain_var_clause(expr)) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("cannot use column references in default expression"))); + if (attgenerated) + check_nested_generated(pstate, expr); + + if (attgenerated && contain_mutable_functions(expr)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("generation expression is not immutable"))); + /* * transformExpr() should have already rejected subqueries, aggregates, * window functions, and SRFs, based on the EXPR_KIND_ for a default diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql index 94e482596f..16677e78d6 100644 --- a/src/backend/catalog/information_schema.sql +++ b/src/backend/catalog/information_schema.sql @@ -509,7 +509,29 @@ CREATE VIEW collation_character_set_applicability AS * COLUMN_COLUMN_USAGE view */ --- feature not supported +CREATE VIEW column_column_usage AS + SELECT CAST(current_database() AS sql_identifier) AS table_catalog, + CAST(n.nspname AS sql_identifier) AS table_schema, + CAST(c.relname AS sql_identifier) AS table_name, + CAST(ac.attname AS sql_identifier) AS column_name, + CAST(ad.attname AS sql_identifier) AS dependent_column + + FROM pg_namespace n, pg_class c, pg_depend d, + pg_attribute ac, pg_attribute ad + + WHERE n.oid = c.relnamespace + AND c.oid = ac.attrelid + AND c.oid = ad.attrelid + AND d.classid = 'pg_catalog.pg_class'::regclass + AND d.refclassid = 'pg_catalog.pg_class'::regclass + AND d.objid = d.refobjid + AND c.oid = d.objid + AND d.objsubid = ad.attnum + AND d.refobjsubid = ac.attnum + AND ad.attgenerated <> '' + AND pg_has_role(c.relowner, 'USAGE'); + +GRANT SELECT ON column_column_usage TO PUBLIC; /* @@ -656,7 +678,7 @@ CREATE VIEW columns AS CAST(c.relname AS sql_identifier) AS table_name, CAST(a.attname AS sql_identifier) AS column_name, CAST(a.attnum AS cardinal_number) AS ordinal_position, - CAST(pg_get_expr(ad.adbin, ad.adrelid) AS character_data) AS column_default, + CAST(CASE WHEN a.attgenerated = '' THEN pg_get_expr(ad.adbin, ad.adrelid) END AS character_data) AS column_default, CAST(CASE WHEN a.attnotnull OR (t.typtype = 'd' AND t.typnotnull) THEN 'NO' ELSE 'YES' END AS yes_or_no) AS is_nullable, @@ -745,8 +767,8 @@ CREATE VIEW columns AS CAST(seq.seqmin AS character_data) AS identity_minimum, CAST(CASE WHEN seq.seqcycle THEN 'YES' ELSE 'NO' END AS yes_or_no) AS identity_cycle, - CAST('NEVER' AS character_data) AS is_generated, - CAST(null AS character_data) AS generation_expression, + CAST(CASE WHEN a.attgenerated <> '' THEN 'ALWAYS' ELSE 'NEVER' END AS character_data) AS is_generated, + CAST(CASE WHEN a.attgenerated <> '' THEN pg_get_expr(ad.adbin, ad.adrelid) END AS character_data) AS generation_expression, CAST(CASE WHEN c.relkind IN ('r', 'p') OR (c.relkind IN ('v', 'f') AND diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 218a6e01cb..9b8cfa4fed 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -32,6 +32,7 @@ #include "commands/trigger.h" #include "executor/execPartition.h" #include "executor/executor.h" +#include "executor/nodeModifyTable.h" #include "executor/tuptable.h" #include "foreign/fdwapi.h" #include "libpq/libpq.h" @@ -2922,6 +2923,21 @@ CopyFrom(CopyState cstate) } else { + /* + * Compute stored generated columns + * + * Switch memory context so that the new tuple is in the same + * context as the old one. + */ + if (resultRelInfo->ri_RelationDesc->rd_att->constr && + resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored) + { + ExecComputeStoredGenerated(estate, slot); + MemoryContextSwitchTo(batchcontext); + tuple = ExecCopySlotHeapTuple(slot); + MemoryContextSwitchTo(oldcontext); + } + /* * If the target is a plain table, check the constraints of * the tuple. @@ -3272,7 +3288,7 @@ BeginCopyFrom(ParseState *pstate, fmgr_info(in_func_oid, &in_functions[attnum - 1]); /* Get default info if needed */ - if (!list_member_int(cstate->attnumlist, attnum)) + if (!list_member_int(cstate->attnumlist, attnum) && !att->attgenerated) { /* attribute is NOT to be copied from input */ /* use default value if one exists */ @@ -4877,6 +4893,11 @@ CopyAttributeOutCSV(CopyState cstate, char *string, * or NIL if there was none (in which case we want all the non-dropped * columns). * + * We don't include generated columns in the generated full list and we don't + * allow them to be specified explicitly. They don't make sense for COPY + * FROM, but we could possibly allow them for COPY TO. But this way it's at + * least ensured that whatever we copy out can be copied back in. + * * rel can be NULL ... it's only used for error reports. */ static List * @@ -4894,6 +4915,8 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist) { if (TupleDescAttr(tupDesc, i)->attisdropped) continue; + if (TupleDescAttr(tupDesc, i)->attgenerated) + continue; attnums = lappend_int(attnums, i + 1); } } @@ -4918,6 +4941,12 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist) continue; if (namestrcmp(&(att->attname), name) == 0) { + if (att->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("column \"%s\" is a generated column", + name), + errdetail("Generated columns cannot be used in COPY."))); attnum = att->attnum; break; } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 515c29072c..47bb1232dd 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -760,6 +760,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, rawEnt->attnum = attnum; rawEnt->raw_default = colDef->raw_default; rawEnt->missingMode = false; + rawEnt->generated = colDef->generated; rawDefaults = lappend(rawDefaults, rawEnt); attr->atthasdef = true; } @@ -783,6 +784,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (colDef->identity) attr->attidentity = colDef->identity; + + if (colDef->generated) + attr->attgenerated = colDef->generated; } /* @@ -863,6 +867,27 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, */ rel = relation_open(relationId, AccessExclusiveLock); + /* + * Now add any newly specified column default and generation expressions + * to the new relation. These are passed to us in the form of raw + * parsetrees; we need to transform them to executable expression trees + * before they can be added. The most convenient way to do that is to + * apply the parser's transformExpr routine, but transformExpr doesn't + * work unless we have a pre-existing relation. So, the transformation has + * to be postponed to this final step of CREATE TABLE. + * + * This needs to be before processing the partitioning clauses because + * those could refer to generated columns. + */ + if (rawDefaults) + AddRelationNewConstraints(rel, rawDefaults, NIL, + true, true, false, queryString); + + /* + * Make column generation expressions visible for use by partitioning. + */ + CommandCounterIncrement(); + /* Process and store partition bound, if any. */ if (stmt->partbound) { @@ -1064,16 +1089,12 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * Now add any newly specified column default values and CHECK constraints - * to the new relation. These are passed to us in the form of raw - * parsetrees; we need to transform them to executable expression trees - * before they can be added. The most convenient way to do that is to - * apply the parser's transformExpr routine, but transformExpr doesn't - * work unless we have a pre-existing relation. So, the transformation has - * to be postponed to this final step of CREATE TABLE. + * Now add any newly specified CHECK constraints to the new relation. + * Same as for defaults above, but these need to come after partitioning + * is set up. */ - if (rawDefaults || stmt->constraints) - AddRelationNewConstraints(rel, rawDefaults, stmt->constraints, + if (stmt->constraints) + AddRelationNewConstraints(rel, NIL, stmt->constraints, true, true, false, queryString); ObjectAddressSet(address, RelationRelationId, relationId); @@ -2232,6 +2253,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence, def->is_not_null |= attribute->attnotnull; /* Default and other constraints are handled below */ newattno[parent_attno - 1] = exist_attno; + + /* Check for GENERATED conflicts */ + if (def->generated != attribute->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("inherited column \"%s\" has a generation conflict", + attributeName))); } else { @@ -2249,6 +2277,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, def->storage = attribute->attstorage; def->raw_default = NULL; def->cooked_default = NULL; + def->generated = attribute->attgenerated; def->collClause = NULL; def->collOid = attribute->attcollation; def->constraints = NIL; @@ -5599,6 +5628,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, attribute.atthasdef = false; attribute.atthasmissing = false; attribute.attidentity = colDef->identity; + attribute.attgenerated = colDef->generated; attribute.attisdropped = false; attribute.attislocal = colDef->is_local; attribute.attinhcount = colDef->inhcount; @@ -5644,7 +5674,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, * DEFAULT value outside of the heap. This may be disabled inside * AddRelationNewConstraints if the optimization cannot be applied. */ - rawEnt->missingMode = true; + rawEnt->missingMode = (!colDef->generated); + + rawEnt->generated = colDef->generated; /* * This function is intended for CREATE TABLE, so it processes a @@ -6225,6 +6257,12 @@ ATExecColumnDefault(Relation rel, const char *colName, colName, RelationGetRelationName(rel)), newDefault ? 0 : errhint("Use ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY instead."))); + if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("column \"%s\" of relation \"%s\" is a generated column", + colName, RelationGetRelationName(rel)))); + /* * Remove any old default for the column. We use RESTRICT here for * safety, but at present we do not expect anything to depend on the @@ -6246,6 +6284,7 @@ ATExecColumnDefault(Relation rel, const char *colName, rawEnt->attnum = attnum; rawEnt->raw_default = newDefault; rawEnt->missingMode = false; + rawEnt->generated = '\0'; /* * This function is intended for CREATE TABLE, so it processes a @@ -7546,6 +7585,32 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, */ checkFkeyPermissions(pkrel, pkattnum, numpks); + /* + * Check some things for generated columns. + */ + for (i = 0; i < numfks; i++) + { + char attgenerated = TupleDescAttr(RelationGetDescr(rel), fkattnum[i] - 1)->attgenerated; + + if (attgenerated) + { + /* + * Check restrictions on UPDATE/DELETE actions, per SQL standard + */ + if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL || + fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT || + fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid ON UPDATE action for foreign key constraint containing generated column"))); + if (fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL || + fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid ON DELETE action for foreign key constraint containing generated column"))); + } + } + /* * Look up the equality operators to use in the constraint. * @@ -9937,10 +10002,18 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, COERCE_IMPLICIT_CAST, -1); if (defaultexpr == NULL) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("default for column \"%s\" cannot be cast automatically to type %s", - colName, format_type_be(targettype)))); + { + if (attTup->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s", + colName, format_type_be(targettype)))); + else + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("default for column \"%s\" cannot be cast automatically to type %s", + colName, format_type_be(targettype)))); + } } else defaultexpr = NULL; @@ -10016,6 +10089,21 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, */ Assert(foundObject.objectSubId == 0); } + else if (relKind == RELKIND_RELATION && + foundObject.objectSubId != 0 && + get_attgenerated(foundObject.objectId, foundObject.objectSubId)) + { + /* + * Changing the type of a column that is used by a + * generated column is not allowed by SQL standard. + * It might be doable with some thinking and effort. + */ + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot alter type of a column used by a generated column"), + errdetail("Column \"%s\" is used by generated column \"%s\".", + colName, get_attname(foundObject.objectId, foundObject.objectSubId, false)))); + } else { /* Not expecting any other direct dependencies... */ @@ -10160,7 +10248,8 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, /* * Now scan for dependencies of this column on other things. The only * thing we should find is the dependency on the column datatype, which we - * want to remove, and possibly a collation dependency. + * want to remove, possibly a collation dependency, and dependencies on + * other columns if it is a generated column. */ ScanKeyInit(&key[0], Anum_pg_depend_classid, @@ -10181,15 +10270,26 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, while (HeapTupleIsValid(depTup = systable_getnext(scan))) { Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup); + ObjectAddress foundObject; - if (foundDep->deptype != DEPENDENCY_NORMAL) + foundObject.classId = foundDep->refclassid; + foundObject.objectId = foundDep->refobjid; + foundObject.objectSubId = foundDep->refobjsubid; + + if (foundDep->deptype != DEPENDENCY_NORMAL && + foundDep->deptype != DEPENDENCY_AUTO) elog(ERROR, "found unexpected dependency type '%c'", foundDep->deptype); if (!(foundDep->refclassid == TypeRelationId && foundDep->refobjid == attTup->atttypid) && !(foundDep->refclassid == CollationRelationId && - foundDep->refobjid == attTup->attcollation)) - elog(ERROR, "found unexpected dependency for column"); + foundDep->refobjid == attTup->attcollation) && + !(foundDep->refclassid == RelationRelationId && + foundDep->refobjid == RelationGetRelid(rel) && + foundDep->refobjsubid != 0) + ) + elog(ERROR, "found unexpected dependency for column: %s", + getObjectDescription(&foundObject)); CatalogTupleDelete(depRel, &depTup->t_self); } @@ -14321,6 +14421,18 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu pelem->name), parser_errposition(pstate, pelem->location))); + /* + * Generated columns cannot work: They are computed after BEFORE + * triggers, but partition routing is done before all triggers. + */ + if (attform->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot use generated column in partition key"), + errdetail("Column \"%s\" is a generated column.", + pelem->name), + parser_errposition(pstate, pelem->location))); + partattrs[attn] = attform->attnum; atttype = attform->atttypid; attcollation = attform->attcollation; @@ -14408,6 +14520,25 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu errmsg("partition key expressions cannot contain system column references"))); } + /* + * Generated columns cannot work: They are computed after + * BEFORE triggers, but partition routing is done before all + * triggers. + */ + i = -1; + while ((i = bms_next_member(expr_attrs, i)) >= 0) + { + AttrNumber attno = i + FirstLowInvalidHeapAttributeNumber; + + if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot use generated column in partition key"), + errdetail("Column \"%s\" is a generated column.", + get_attname(RelationGetRelid(rel), attno, false)), + parser_errposition(pstate, pelem->location))); + } + /* * While it is not exactly *wrong* for a partition expression * to be a constant, it seems better to reject such keys. diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 7109889694..bafcffee18 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -73,8 +73,9 @@ static int MyTriggerDepth = 0; * they use, so we let them be duplicated. Be sure to update all if one needs * to be changed, however. */ -#define GetUpdatedColumns(relinfo, estate) \ - (exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols) +#define GetAllUpdatedColumns(relinfo, estate) \ + (bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \ + exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols)) /* Local function prototypes */ static void ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid); @@ -638,6 +639,24 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("BEFORE trigger's WHEN condition cannot reference NEW system columns"), parser_errposition(pstate, var->location))); + if (TRIGGER_FOR_BEFORE(tgtype) && + var->varattno == 0 && + RelationGetDescr(rel)->constr && + RelationGetDescr(rel)->constr->has_generated_stored) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"), + errdetail("A whole-row reference is used and the table contains generated columns."), + parser_errposition(pstate, var->location))); + if (TRIGGER_FOR_BEFORE(tgtype) && + var->varattno > 0 && + TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"), + errdetail("Column \"%s\" is a generated column.", + NameStr(TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attname)), + parser_errposition(pstate, var->location))); break; default: /* can't happen without add_missing_from, so just elog */ @@ -2929,7 +2948,7 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo) CMD_UPDATE)) return; - updatedCols = GetUpdatedColumns(relinfo, estate); + updatedCols = GetAllUpdatedColumns(relinfo, estate); LocTriggerData.type = T_TriggerData; LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE | @@ -2978,7 +2997,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo, if (trigdesc && trigdesc->trig_update_after_statement) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, false, NULL, NULL, NIL, - GetUpdatedColumns(relinfo, estate), + GetAllUpdatedColumns(relinfo, estate), transition_capture); } @@ -3047,7 +3066,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, LocTriggerData.tg_relation = relinfo->ri_RelationDesc; LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_newtable = NULL; - updatedCols = GetUpdatedColumns(relinfo, estate); + updatedCols = GetAllUpdatedColumns(relinfo, estate); for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; @@ -3138,7 +3157,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, true, oldslot, newslot, recheckIndexes, - GetUpdatedColumns(relinfo, estate), + GetAllUpdatedColumns(relinfo, estate), transition_capture); } } diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index f94248dc95..7e6bcc5239 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -918,7 +918,8 @@ DefineDomain(CreateDomainStmt *stmt) defaultExpr = cookDefault(pstate, constr->raw_expr, basetypeoid, basetypeMod, - domainName); + domainName, + 0); /* * If the expression is just a NULL constant, we treat it @@ -2228,7 +2229,8 @@ AlterDomainDefault(List *names, Node *defaultRaw) defaultExpr = cookDefault(pstate, defaultRaw, typTup->typbasetype, typTup->typtypmod, - NameStr(typTup->typname)); + NameStr(typTup->typname), + 0); /* * If the expression is just a NULL constant, we treat the command diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 63a34760ee..0edd1d703b 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -102,7 +102,7 @@ static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree); /* - * Note that GetUpdatedColumns() also exists in commands/trigger.c. There does + * Note that GetAllUpdatedColumns() also exists in commands/trigger.c. There does * not appear to be any good header to put it into, given the structures that * it uses, so we let them be duplicated. Be sure to update both if one needs * to be changed, however. @@ -111,6 +111,9 @@ static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate, (exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->insertedCols) #define GetUpdatedColumns(relinfo, estate) \ (exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols) +#define GetAllUpdatedColumns(relinfo, estate) \ + (bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \ + exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols)) /* end of local decls */ @@ -1316,6 +1319,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, resultRelInfo->ri_FdwState = NULL; resultRelInfo->ri_usesFdwDirectModify = false; resultRelInfo->ri_ConstraintExprs = NULL; + resultRelInfo->ri_GeneratedExprs = NULL; resultRelInfo->ri_junkFilter = NULL; resultRelInfo->ri_projectReturning = NULL; resultRelInfo->ri_onConflictArbiterIndexes = NIL; @@ -2328,7 +2332,7 @@ ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo) * been modified, then we can use a weaker lock, allowing for better * concurrency. */ - updatedCols = GetUpdatedColumns(relinfo, estate); + updatedCols = GetAllUpdatedColumns(relinfo, estate); keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc, INDEX_ATTR_BITMAP_KEY); diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index 95dfc4987d..62cd97ea4b 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -22,6 +22,7 @@ #include "access/xact.h" #include "commands/trigger.h" #include "executor/executor.h" +#include "executor/nodeModifyTable.h" #include "nodes/nodeFuncs.h" #include "parser/parse_relation.h" #include "parser/parsetree.h" @@ -413,6 +414,11 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) { List *recheckIndexes = NIL; + /* Compute stored generated columns */ + if (rel->rd_att->constr && + rel->rd_att->constr->has_generated_stored) + ExecComputeStoredGenerated(estate, slot); + /* Check the constraints of the tuple */ if (rel->rd_att->constr) ExecConstraints(resultRelInfo, slot, estate); @@ -483,6 +489,11 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, { List *recheckIndexes = NIL; + /* Compute stored generated columns */ + if (rel->rd_att->constr && + rel->rd_att->constr->has_generated_stored) + ExecComputeStoredGenerated(estate, slot); + /* Check the constraints of the tuple */ if (rel->rd_att->constr) ExecConstraints(resultRelInfo, slot, estate); diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index fa92db130b..28c3a96aed 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -49,6 +49,7 @@ #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "rewrite/rewriteHandler.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" #include "utils/builtins.h" @@ -240,6 +241,89 @@ ExecCheckTIDVisible(EState *estate, ReleaseBuffer(buffer); } +/* + * Compute stored generated columns for a tuple + */ +void +ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot) +{ + ResultRelInfo *resultRelInfo = estate->es_result_relation_info; + Relation rel = resultRelInfo->ri_RelationDesc; + TupleDesc tupdesc = RelationGetDescr(rel); + int natts = tupdesc->natts; + MemoryContext oldContext; + Datum *values; + bool *nulls; + bool *replaces; + HeapTuple oldtuple, newtuple; + bool should_free; + + Assert(tupdesc->constr && tupdesc->constr->has_generated_stored); + + /* + * If first time through for this result relation, build expression + * nodetrees for rel's stored generation expressions. Keep them in the + * per-query memory context so they'll survive throughout the query. + */ + if (resultRelInfo->ri_GeneratedExprs == NULL) + { + oldContext = MemoryContextSwitchTo(estate->es_query_cxt); + + resultRelInfo->ri_GeneratedExprs = + (ExprState **) palloc(natts * sizeof(ExprState *)); + + for (int i = 0; i < natts; i++) + { + if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED) + { + Expr *expr; + + expr = (Expr *) build_column_default(rel, i + 1); + if (expr == NULL) + elog(ERROR, "no generation expression found for column number %d of table \"%s\"", + i + 1, RelationGetRelationName(rel)); + + resultRelInfo->ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate); + } + } + + MemoryContextSwitchTo(oldContext); + } + + oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); + + values = palloc(sizeof(*values) * natts); + nulls = palloc(sizeof(*nulls) * natts); + replaces = palloc0(sizeof(*replaces) * natts); + + for (int i = 0; i < natts; i++) + { + if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED) + { + ExprContext *econtext; + Datum val; + bool isnull; + + econtext = GetPerTupleExprContext(estate); + econtext->ecxt_scantuple = slot; + + val = ExecEvalExpr(resultRelInfo->ri_GeneratedExprs[i], econtext, &isnull); + + values[i] = val; + nulls[i] = isnull; + replaces[i] = true; + } + } + + oldtuple = ExecFetchSlotHeapTuple(slot, true, &should_free); + newtuple = heap_modify_tuple(oldtuple, tupdesc, values, nulls, replaces); + ExecForceStoreHeapTuple(newtuple, slot); + if (should_free) + heap_freetuple(oldtuple); + + MemoryContextSwitchTo(oldContext); +} + /* ---------------------------------------------------------------- * ExecInsert * @@ -297,6 +381,13 @@ ExecInsert(ModifyTableState *mtstate, } else if (resultRelInfo->ri_FdwRoutine) { + /* + * Compute stored generated columns + */ + if (resultRelationDesc->rd_att->constr && + resultRelationDesc->rd_att->constr->has_generated_stored) + ExecComputeStoredGenerated(estate, slot); + /* * insert into foreign table: let the FDW do it */ @@ -327,6 +418,13 @@ ExecInsert(ModifyTableState *mtstate, */ slot->tts_tableOid = RelationGetRelid(resultRelationDesc); + /* + * Compute stored generated columns + */ + if (resultRelationDesc->rd_att->constr && + resultRelationDesc->rd_att->constr->has_generated_stored) + ExecComputeStoredGenerated(estate, slot); + /* * Check any RLS WITH CHECK policies. * @@ -938,6 +1036,13 @@ ExecUpdate(ModifyTableState *mtstate, } else if (resultRelInfo->ri_FdwRoutine) { + /* + * Compute stored generated columns + */ + if (resultRelationDesc->rd_att->constr && + resultRelationDesc->rd_att->constr->has_generated_stored) + ExecComputeStoredGenerated(estate, slot); + /* * update in foreign table: let the FDW do it */ @@ -967,6 +1072,13 @@ ExecUpdate(ModifyTableState *mtstate, */ slot->tts_tableOid = RelationGetRelid(resultRelationDesc); + /* + * Compute stored generated columns + */ + if (resultRelationDesc->rd_att->constr && + resultRelationDesc->rd_att->constr->has_generated_stored) + ExecComputeStoredGenerated(estate, slot); + /* * Check any RLS UPDATE WITH CHECK policies * diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index a8a735c247..573750652c 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2383,6 +2383,7 @@ _copyRangeTblEntry(const RangeTblEntry *from) COPY_BITMAPSET_FIELD(selectedCols); COPY_BITMAPSET_FIELD(insertedCols); COPY_BITMAPSET_FIELD(updatedCols); + COPY_BITMAPSET_FIELD(extraUpdatedCols); COPY_NODE_FIELD(securityQuals); return newnode; @@ -2881,6 +2882,7 @@ _copyColumnDef(const ColumnDef *from) COPY_NODE_FIELD(cooked_default); COPY_SCALAR_FIELD(identity); COPY_NODE_FIELD(identitySequence); + COPY_SCALAR_FIELD(generated); COPY_NODE_FIELD(collClause); COPY_SCALAR_FIELD(collOid); COPY_NODE_FIELD(constraints); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 3cab90e9f8..06782e57c8 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2561,6 +2561,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b) COMPARE_NODE_FIELD(cooked_default); COMPARE_SCALAR_FIELD(identity); COMPARE_NODE_FIELD(identitySequence); + COMPARE_SCALAR_FIELD(generated); COMPARE_NODE_FIELD(collClause); COMPARE_SCALAR_FIELD(collOid); COMPARE_NODE_FIELD(constraints); @@ -2660,6 +2661,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b) COMPARE_BITMAPSET_FIELD(selectedCols); COMPARE_BITMAPSET_FIELD(insertedCols); COMPARE_BITMAPSET_FIELD(updatedCols); + COMPARE_BITMAPSET_FIELD(extraUpdatedCols); COMPARE_NODE_FIELD(securityQuals); return true; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 69179a07c3..8a0185cfa5 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2785,6 +2785,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node) WRITE_NODE_FIELD(cooked_default); WRITE_CHAR_FIELD(identity); WRITE_NODE_FIELD(identitySequence); + WRITE_CHAR_FIELD(generated); WRITE_NODE_FIELD(collClause); WRITE_OID_FIELD(collOid); WRITE_NODE_FIELD(constraints); @@ -3089,6 +3090,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) WRITE_BITMAPSET_FIELD(selectedCols); WRITE_BITMAPSET_FIELD(insertedCols); WRITE_BITMAPSET_FIELD(updatedCols); + WRITE_BITMAPSET_FIELD(extraUpdatedCols); WRITE_NODE_FIELD(securityQuals); } @@ -3460,6 +3462,13 @@ _outConstraint(StringInfo str, const Constraint *node) WRITE_CHAR_FIELD(generated_when); break; + case CONSTR_GENERATED: + appendStringInfoString(str, "GENERATED"); + WRITE_NODE_FIELD(raw_expr); + WRITE_STRING_FIELD(cooked_expr); + WRITE_CHAR_FIELD(generated_when); + break; + case CONSTR_CHECK: appendStringInfoString(str, "CHECK"); WRITE_BOOL_FIELD(is_no_inherit); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 4b845b1bb7..a517b7ba62 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1430,6 +1430,7 @@ _readRangeTblEntry(void) READ_BITMAPSET_FIELD(selectedCols); READ_BITMAPSET_FIELD(insertedCols); READ_BITMAPSET_FIELD(updatedCols); + READ_BITMAPSET_FIELD(extraUpdatedCols); READ_NODE_FIELD(securityQuals); READ_DONE(); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 9fbe5b2a5f..552d696cb4 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -6530,8 +6530,9 @@ make_modifytable(PlannerInfo *root, /* * Try to modify the foreign table directly if (1) the FDW provides - * callback functions needed for that, (2) there are no row-level - * triggers on the foreign table, and (3) there are no WITH CHECK + * callback functions needed for that and (2) there are no local + * structures that need to be run for each modified row: row-level + * triggers on the foreign table, stored generated columns, WITH CHECK * OPTIONs from parent views. */ direct_modify = false; @@ -6541,7 +6542,8 @@ make_modifytable(PlannerInfo *root, fdwroutine->IterateDirectModify != NULL && fdwroutine->EndDirectModify != NULL && withCheckOptionLists == NIL && - !has_row_triggers(subroot, rti, operation)) + !has_row_triggers(subroot, rti, operation) && + !has_stored_generated_columns(subroot, rti)) direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i); if (direct_modify) direct_modify_plans = bms_add_member(direct_modify_plans, i); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 1fa154e0cb..a811f6a547 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -272,6 +272,10 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, if (!root->partColsUpdated) root->partColsUpdated = has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + /* + * There shouldn't be any generated columns in the partition key. + */ + Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL)); /* First expand the partitioned table itself. */ expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, @@ -412,6 +416,8 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, appinfo->translated_vars); childrte->updatedCols = translate_col_privs(parentrte->updatedCols, appinfo->translated_vars); + childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols, + appinfo->translated_vars); } } diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 30f4dc151b..0a86f619c0 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -2072,6 +2072,25 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event) return result; } +bool +has_stored_generated_columns(PlannerInfo *root, Index rti) +{ + RangeTblEntry *rte = planner_rt_fetch(rti, root); + Relation relation; + TupleDesc tupdesc; + bool result = false; + + /* Assume we already have adequate lock */ + relation = heap_open(rte->relid, NoLock); + + tupdesc = RelationGetDescr(relation); + result = tupdesc->constr && tupdesc->constr->has_generated_stored; + + heap_close(relation, NoLock); + + return result; +} + /* * set_relation_partition_info * diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index d6cdd16607..400558b552 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -2287,6 +2287,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist) RangeTblEntry *target_rte; ListCell *orig_tl; ListCell *tl; + TupleDesc tupdesc = pstate->p_target_relation->rd_att; tlist = transformTargetList(pstate, origTlist, EXPR_KIND_UPDATE_SOURCE); @@ -2345,6 +2346,32 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist) if (orig_tl != NULL) elog(ERROR, "UPDATE target count mismatch --- internal error"); + /* + * Record in extraUpdatedCols generated columns referencing updated base + * columns. + */ + if (tupdesc->constr && + tupdesc->constr->has_generated_stored) + { + for (int i = 0; i < tupdesc->constr->num_defval; i++) + { + AttrDefault defval = tupdesc->constr->defval[i]; + Node *expr; + Bitmapset *attrs_used = NULL; + + /* skip if not generated column */ + if (!TupleDescAttr(tupdesc, defval.adnum - 1)->attgenerated) + continue; + + expr = stringToNode(defval.adbin); + pull_varattnos(expr, 1, &attrs_used); + + if (bms_overlap(target_rte->updatedCols, attrs_used)) + target_rte->extraUpdatedCols = bms_add_member(target_rte->extraUpdatedCols, + defval.adnum - FirstLowInvalidHeapAttributeNumber); + } + } + return tlist; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e23e68fdb3..860908af58 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -678,7 +678,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P - START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P + START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN @@ -3494,6 +3494,16 @@ ColConstraintElem: n->location = @1; $$ = (Node *)n; } + | GENERATED generated_when AS '(' a_expr ')' STORED + { + Constraint *n = makeNode(Constraint); + n->contype = CONSTR_GENERATED; + n->generated_when = $2; + n->raw_expr = $5; + n->cooked_expr = NULL; + n->location = @1; + $$ = (Node *)n; + } | REFERENCES qualified_name opt_column_list key_match key_actions { Constraint *n = makeNode(Constraint); @@ -3584,6 +3594,7 @@ TableLikeOption: | CONSTRAINTS { $$ = CREATE_TABLE_LIKE_CONSTRAINTS; } | DEFAULTS { $$ = CREATE_TABLE_LIKE_DEFAULTS; } | IDENTITY_P { $$ = CREATE_TABLE_LIKE_IDENTITY; } + | GENERATED { $$ = CREATE_TABLE_LIKE_GENERATED; } | INDEXES { $$ = CREATE_TABLE_LIKE_INDEXES; } | STATISTICS { $$ = CREATE_TABLE_LIKE_STATISTICS; } | STORAGE { $$ = CREATE_TABLE_LIKE_STORAGE; } @@ -15221,6 +15232,7 @@ unreserved_keyword: | STDIN | STDOUT | STORAGE + | STORED | STRICT_P | STRIP_P | SUBSCRIPTION diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 183ea0f2c4..c745fcdd2b 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -520,6 +520,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) err = _("grouping operations are not allowed in partition key expressions"); break; + case EXPR_KIND_GENERATED_COLUMN: + + if (isAgg) + err = _("aggregate functions are not allowed in column generation expressions"); + else + err = _("grouping operations are not allowed in column generation expressions"); + + break; case EXPR_KIND_CALL_ARGUMENT: if (isAgg) @@ -922,6 +930,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, case EXPR_KIND_COPY_WHERE: err = _("window functions are not allowed in COPY FROM WHERE conditions"); break; + case EXPR_KIND_GENERATED_COLUMN: + err = _("window functions are not allowed in column generation expressions"); + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index e559353529..0c88d0b2ee 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1854,6 +1854,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_COPY_WHERE: err = _("cannot use subquery in COPY FROM WHERE condition"); break; + case EXPR_KIND_GENERATED_COLUMN: + err = _("cannot use subquery in column generation expression"); + break; /* * There is intentionally no default: case here, so that the @@ -3484,6 +3487,8 @@ ParseExprKindName(ParseExprKind exprKind) return "CALL"; case EXPR_KIND_COPY_WHERE: return "WHERE"; + case EXPR_KIND_GENERATED_COLUMN: + return "GENERATED AS"; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 654ee80b27..cc0e6b0180 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2375,6 +2375,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) case EXPR_KIND_COPY_WHERE: err = _("set-returning functions are not allowed in COPY FROM WHERE conditions"); break; + case EXPR_KIND_GENERATED_COLUMN: + err = _("set-returning functions are not allowed in column generation expressions"); + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index f3b6d193aa..0640d11fac 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -731,6 +731,17 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, const char *colname, colname), parser_errposition(pstate, location))); + /* + * In generated column, no system column is allowed except tableOid. + */ + if (pstate->p_expr_kind == EXPR_KIND_GENERATED_COLUMN && + attnum < InvalidAttrNumber && attnum != TableOidAttributeNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("cannot use system column \"%s\" in column generation expression", + colname), + parser_errposition(pstate, location))); + if (attnum != InvalidAttrNumber) { /* now check to see if column actually is defined */ @@ -1257,6 +1268,7 @@ addRangeTableEntry(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1328,6 +1340,7 @@ addRangeTableEntryForRelation(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1407,6 +1420,7 @@ addRangeTableEntryForSubquery(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1670,6 +1684,7 @@ addRangeTableEntryForFunction(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1733,6 +1748,7 @@ addRangeTableEntryForTableFunc(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1811,6 +1827,7 @@ addRangeTableEntryForValues(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1881,6 +1898,7 @@ addRangeTableEntryForJoin(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1983,6 +2001,7 @@ addRangeTableEntryForCTE(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index a37d1f18be..9e28e8b673 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -502,6 +502,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) bool saw_nullable; bool saw_default; bool saw_identity; + bool saw_generated; ListCell *clist; cxt->columns = lappend(cxt->columns, column); @@ -609,6 +610,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) saw_nullable = false; saw_default = false; saw_identity = false; + saw_generated = false; foreach(clist, column->constraints) { @@ -689,6 +691,29 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) break; } + case CONSTR_GENERATED: + if (cxt->ofType) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("generated columns are not supported on typed tables"))); + if (cxt->partbound) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("generated columns are not supported on partitions"))); + + if (saw_generated) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple generation clauses specified for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); + column->generated = ATTRIBUTE_GENERATED_STORED; + column->raw_default = constraint->raw_expr; + Assert(constraint->cooked_expr == NULL); + saw_generated = true; + break; + case CONSTR_CHECK: cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); break; @@ -755,6 +780,22 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) column->colname, cxt->relation->relname), parser_errposition(cxt->pstate, constraint->location))); + + if (saw_default && saw_generated) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("both default and generation expression specified for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); + + if (saw_identity && saw_generated) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("both identity and generation expression specified for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); } /* @@ -983,11 +1024,13 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla * Copy default, if present and the default has been requested */ if (attribute->atthasdef && - (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS)) + (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS || + table_like_clause->options & CREATE_TABLE_LIKE_GENERATED)) { Node *this_default = NULL; AttrDefault *attrdef; int i; + bool found_whole_row; /* Find default in constraint structure */ Assert(constr != NULL); @@ -1002,12 +1045,27 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla } Assert(this_default != NULL); + def->cooked_default = map_variable_attnos(this_default, + 1, 0, + attmap, tupleDesc->natts, + InvalidOid, &found_whole_row); + /* - * If default expr could contain any vars, we'd need to fix 'em, - * but it can't; so default is ready to apply to child. + * Prevent this for the same reason as for constraints below. + * Note that defaults cannot contain any vars, so it's OK that the + * error message refers to generated columns. */ + if (found_whole_row) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert whole-row table reference"), + errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".", + attributeName, + RelationGetRelationName(relation)))); - def->cooked_default = this_default; + if (attribute->attgenerated && + (table_like_clause->options & CREATE_TABLE_LIKE_GENERATED)) + def->generated = attribute->attgenerated; } /* diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c index dffb6cd9fd..0411963f93 100644 --- a/src/backend/replication/logical/proto.c +++ b/src/backend/replication/logical/proto.c @@ -453,7 +453,7 @@ logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple) for (i = 0; i < desc->natts; i++) { - if (TupleDescAttr(desc, i)->attisdropped) + if (TupleDescAttr(desc, i)->attisdropped || TupleDescAttr(desc, i)->attgenerated) continue; nliveatts++; } @@ -473,8 +473,7 @@ logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple) Form_pg_attribute att = TupleDescAttr(desc, i); char *outputstr; - /* skip dropped columns */ - if (att->attisdropped) + if (att->attisdropped || att->attgenerated) continue; if (isnull[i]) @@ -573,7 +572,7 @@ logicalrep_write_attrs(StringInfo out, Relation rel) /* send number of live attributes */ for (i = 0; i < desc->natts; i++) { - if (TupleDescAttr(desc, i)->attisdropped) + if (TupleDescAttr(desc, i)->attisdropped || TupleDescAttr(desc, i)->attgenerated) continue; nliveatts++; } @@ -591,7 +590,7 @@ logicalrep_write_attrs(StringInfo out, Relation rel) Form_pg_attribute att = TupleDescAttr(desc, i); uint8 flags = 0; - if (att->attisdropped) + if (att->attisdropped || att->attgenerated) continue; /* REPLICA IDENTITY FULL means all columns are sent as part of key. */ diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c index 1d918d2c42..5aee4b80e6 100644 --- a/src/backend/replication/logical/relation.c +++ b/src/backend/replication/logical/relation.c @@ -276,7 +276,7 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode) int attnum; Form_pg_attribute attr = TupleDescAttr(desc, i); - if (attr->attisdropped) + if (attr->attisdropped || attr->attgenerated) { entry->attrmap[i] = -1; continue; diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c index 28f5fc23aa..7881079e96 100644 --- a/src/backend/replication/logical/tablesync.c +++ b/src/backend/replication/logical/tablesync.c @@ -697,10 +697,12 @@ fetch_remote_table_info(char *nspname, char *relname, " LEFT JOIN pg_catalog.pg_index i" " ON (i.indexrelid = pg_get_replica_identity_index(%u))" " WHERE a.attnum > 0::pg_catalog.int2" - " AND NOT a.attisdropped" + " AND NOT a.attisdropped %s" " AND a.attrelid = %u" " ORDER BY a.attnum", - lrel->remoteid, lrel->remoteid); + lrel->remoteid, + (walrcv_server_version(wrconn) >= 120000 ? "AND a.attgenerated = ''" : ""), + lrel->remoteid); res = walrcv_exec(wrconn, cmd.data, 4, attrRow); if (res->status != WALRCV_OK_TUPLES) diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index 52a5090b69..43edfef089 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -236,7 +236,7 @@ slot_fill_defaults(LogicalRepRelMapEntry *rel, EState *estate, { Expr *defexpr; - if (TupleDescAttr(desc, attnum)->attisdropped) + if (TupleDescAttr(desc, attnum)->attisdropped || TupleDescAttr(desc, attnum)->attgenerated) continue; if (rel->attrmap[attnum] >= 0) diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index 5511957516..bf64c8e4a4 100644 --- a/src/backend/replication/pgoutput/pgoutput.c +++ b/src/backend/replication/pgoutput/pgoutput.c @@ -276,7 +276,7 @@ maybe_send_schema(LogicalDecodingContext *ctx, { Form_pg_attribute att = TupleDescAttr(desc, i); - if (att->attisdropped) + if (att->attisdropped || att->attgenerated) continue; if (att->atttypid < FirstNormalObjectId) diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 4fc50c89b9..39080776b0 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -818,6 +818,13 @@ rewriteTargetListIU(List *targetList, if (att_tup->attidentity == ATTRIBUTE_IDENTITY_BY_DEFAULT && override == OVERRIDING_USER_VALUE) apply_default = true; + + if (att_tup->attgenerated && !apply_default) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot insert into column \"%s\"", NameStr(att_tup->attname)), + errdetail("Column \"%s\" is a generated column.", + NameStr(att_tup->attname)))); } if (commandType == CMD_UPDATE) @@ -828,9 +835,23 @@ rewriteTargetListIU(List *targetList, errmsg("column \"%s\" can only be updated to DEFAULT", NameStr(att_tup->attname)), errdetail("Column \"%s\" is an identity column defined as GENERATED ALWAYS.", NameStr(att_tup->attname)))); + + if (att_tup->attgenerated && new_tle && !apply_default) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("column \"%s\" can only be updated to DEFAULT", NameStr(att_tup->attname)), + errdetail("Column \"%s\" is a generated column.", + NameStr(att_tup->attname)))); } - if (apply_default) + if (att_tup->attgenerated) + { + /* + * stored generated column will be fixed in executor + */ + new_tle = NULL; + } + else if (apply_default) { Node *new_expr; @@ -1137,13 +1158,12 @@ build_column_default(Relation rel, int attrno) } } - if (expr == NULL) - { - /* - * No per-column default, so look for a default for the type itself. - */ + /* + * No per-column default, so look for a default for the type itself. But + * not for generated columns. + */ + if (expr == NULL && !att_tup->attgenerated) expr = get_typdefault(atttype); - } if (expr == NULL) return NULL; /* No default anywhere */ @@ -1720,12 +1740,14 @@ ApplyRetrieveRule(Query *parsetree, subrte->selectedCols = rte->selectedCols; subrte->insertedCols = rte->insertedCols; subrte->updatedCols = rte->updatedCols; + subrte->extraUpdatedCols = rte->extraUpdatedCols; rte->requiredPerms = 0; /* no permission check on subquery itself */ rte->checkAsUser = InvalidOid; rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; return parsetree; } diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index e88c45d268..d98646d0e6 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -821,6 +821,39 @@ get_attnum(Oid relid, const char *attname) return InvalidAttrNumber; } +/* + * get_attgenerated + * + * Given the relation id and the attribute name, + * return the "attgenerated" field from the attribute relation. + * + * Errors if not found. + * + * Since not generated is represented by '\0', this can also be used as a + * Boolean test. + */ +char +get_attgenerated(Oid relid, AttrNumber attnum) +{ + HeapTuple tp; + + tp = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum)); + if (HeapTupleIsValid(tp)) + { + Form_pg_attribute att_tup = (Form_pg_attribute) GETSTRUCT(tp); + char result; + + result = att_tup->attgenerated; + ReleaseSysCache(tp); + return result; + } + else + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, relid); +} + /* * get_atttype * diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c index 2b55f25e75..8f43d682cf 100644 --- a/src/backend/utils/cache/partcache.c +++ b/src/backend/utils/cache/partcache.c @@ -27,6 +27,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" #include "partitioning/partbounds.h" +#include "rewrite/rewriteHandler.h" #include "utils/builtins.h" #include "utils/datum.h" #include "utils/lsyscache.h" diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 84609e0725..4557164b00 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -515,6 +515,7 @@ RelationBuildTupleDesc(Relation relation) constr = (TupleConstr *) MemoryContextAlloc(CacheMemoryContext, sizeof(TupleConstr)); constr->has_not_null = false; + constr->has_generated_stored = false; /* * Form a scan key that selects only user attributes (attnum > 0). @@ -567,6 +568,8 @@ RelationBuildTupleDesc(Relation relation) /* Update constraint/default info */ if (attp->attnotnull) constr->has_not_null = true; + if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED) + constr->has_generated_stored = true; /* If the column has a default, fill it into the attrdef array */ if (attp->atthasdef) @@ -3281,6 +3284,7 @@ RelationBuildLocalRelation(const char *relname, Form_pg_attribute datt = TupleDescAttr(rel->rd_att, i); datt->attidentity = satt->attidentity; + datt->attgenerated = satt->attgenerated; datt->attnotnull = satt->attnotnull; has_not_null |= satt->attnotnull; } diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 4c98ae4d7f..6dbf707995 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -2051,6 +2051,11 @@ dumpTableData_insert(Archive *fout, void *dcontext) { if (field > 0) archputs(", ", fout); + if (tbinfo->attgenerated[field]) + { + archputs("DEFAULT", fout); + continue; + } if (PQgetisnull(res, tuple, field)) { archputs("NULL", fout); @@ -8219,6 +8224,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) int i_attnotnull; int i_atthasdef; int i_attidentity; + int i_attgenerated; int i_attisdropped; int i_attlen; int i_attalign; @@ -8272,6 +8278,13 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) "a.attislocal,\n" "pg_catalog.format_type(t.oid, a.atttypmod) AS atttypname,\n"); + if (fout->remoteVersion >= 120000) + appendPQExpBuffer(q, + "a.attgenerated,\n"); + else + appendPQExpBuffer(q, + "'' AS attgenerated,\n"); + if (fout->remoteVersion >= 110000) appendPQExpBuffer(q, "CASE WHEN a.atthasmissing AND NOT a.attisdropped " @@ -8344,6 +8357,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) i_attnotnull = PQfnumber(res, "attnotnull"); i_atthasdef = PQfnumber(res, "atthasdef"); i_attidentity = PQfnumber(res, "attidentity"); + i_attgenerated = PQfnumber(res, "attgenerated"); i_attisdropped = PQfnumber(res, "attisdropped"); i_attlen = PQfnumber(res, "attlen"); i_attalign = PQfnumber(res, "attalign"); @@ -8361,6 +8375,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) tbinfo->attstorage = (char *) pg_malloc(ntups * sizeof(char)); tbinfo->typstorage = (char *) pg_malloc(ntups * sizeof(char)); tbinfo->attidentity = (char *) pg_malloc(ntups * sizeof(char)); + tbinfo->attgenerated = (char *) pg_malloc(ntups * sizeof(char)); tbinfo->attisdropped = (bool *) pg_malloc(ntups * sizeof(bool)); tbinfo->attlen = (int *) pg_malloc(ntups * sizeof(int)); tbinfo->attalign = (char *) pg_malloc(ntups * sizeof(char)); @@ -8387,6 +8402,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) tbinfo->attstorage[j] = *(PQgetvalue(res, j, i_attstorage)); tbinfo->typstorage[j] = *(PQgetvalue(res, j, i_typstorage)); tbinfo->attidentity[j] = *(PQgetvalue(res, j, i_attidentity)); + tbinfo->attgenerated[j] = *(PQgetvalue(res, j, i_attgenerated)); tbinfo->needs_override = tbinfo->needs_override || (tbinfo->attidentity[j] == ATTRIBUTE_IDENTITY_ALWAYS); tbinfo->attisdropped[j] = (PQgetvalue(res, j, i_attisdropped)[0] == 't'); tbinfo->attlen[j] = atoi(PQgetvalue(res, j, i_attlen)); @@ -15697,6 +15713,20 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) tbinfo->atttypnames[j]); } + if (has_default) + { + if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED) + appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED", + tbinfo->attrdefs[j]->adef_expr); + else + appendPQExpBuffer(q, " DEFAULT %s", + tbinfo->attrdefs[j]->adef_expr); + } + + + if (has_notnull) + appendPQExpBufferStr(q, " NOT NULL"); + /* Add collation if not default for the type */ if (OidIsValid(tbinfo->attcollation[j])) { @@ -15707,13 +15737,6 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) appendPQExpBuffer(q, " COLLATE %s", fmtQualifiedDumpable(coll)); } - - if (has_default) - appendPQExpBuffer(q, " DEFAULT %s", - tbinfo->attrdefs[j]->adef_expr); - - if (has_notnull) - appendPQExpBufferStr(q, " NOT NULL"); } } @@ -18292,6 +18315,7 @@ fmtCopyColumnList(const TableInfo *ti, PQExpBuffer buffer) int numatts = ti->numatts; char **attnames = ti->attnames; bool *attisdropped = ti->attisdropped; + char *attgenerated = ti->attgenerated; bool needComma; int i; @@ -18301,6 +18325,8 @@ fmtCopyColumnList(const TableInfo *ti, PQExpBuffer buffer) { if (attisdropped[i]) continue; + if (attgenerated[i]) + continue; if (needComma) appendPQExpBufferStr(buffer, ", "); appendPQExpBufferStr(buffer, fmtId(attnames[i])); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 2e1b90acd0..a72e3eb27c 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -310,6 +310,7 @@ typedef struct _tableInfo char *typstorage; /* type storage scheme */ bool *attisdropped; /* true if attr is dropped; don't dump it */ char *attidentity; + char *attgenerated; int *attlen; /* attribute length, used by binary_upgrade */ char *attalign; /* attribute align, used by binary_upgrade */ bool *attislocal; /* true if attr has local definition */ diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index bb128c89f3..243dca7264 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -1108,6 +1108,16 @@ repairDependencyLoop(DumpableObject **loop, } } + /* Loop of table with itself, happens with generated columns */ + if (nLoop == 1) + { + if (loop[0]->objType == DO_TABLE) + { + removeObjectDependency(loop[0], loop[0]->dumpId); + return; + } + } + /* * If all the objects are TABLE_DATA items, what we must have is a * circular set of foreign key constraints (or a single self-referential diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index de6895122e..a69375056d 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2392,6 +2392,23 @@ unlike => { exclude_dump_test_schema => 1, }, }, + 'CREATE TABLE test_table_generated' => { + create_order => 3, + create_sql => 'CREATE TABLE dump_test.test_table_generated ( + col1 int primary key, + col2 int generated always as (col1 * 2) stored + );', + regexp => qr/^ + \QCREATE TABLE dump_test.test_table_generated (\E\n + \s+\Qcol1 integer NOT NULL,\E\n + \s+\Qcol2 integer GENERATED ALWAYS AS ((col1 * 2)) STORED\E\n + \); + /xms, + like => + { %full_runs, %dump_test_schema_runs, section_pre_data => 1, }, + unlike => { exclude_dump_test_schema => 1, }, + }, + 'CREATE TABLE table_with_stats' => { create_order => 98, create_sql => 'CREATE TABLE dump_test.table_index_stats ( diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 779e48437c..8355da1f7f 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1462,6 +1462,7 @@ describeOneTableDetails(const char *schemaname, attnotnull_col = -1, attcoll_col = -1, attidentity_col = -1, + attgenerated_col = -1, isindexkey_col = -1, indexdef_col = -1, fdwopts_col = -1, @@ -1813,8 +1814,9 @@ describeOneTableDetails(const char *schemaname, if (show_column_details) { + /* use "pretty" mode for expression to avoid excessive parentheses */ appendPQExpBufferStr(&buf, - ",\n (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128)" + ",\n (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128)" "\n FROM pg_catalog.pg_attrdef d" "\n WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef)" ",\n a.attnotnull"); @@ -1831,6 +1833,11 @@ describeOneTableDetails(const char *schemaname, else appendPQExpBufferStr(&buf, ",\n ''::pg_catalog.char AS attidentity"); attidentity_col = cols++; + if (pset.sversion >= 120000) + appendPQExpBufferStr(&buf, ",\n a.attgenerated"); + else + appendPQExpBufferStr(&buf, ",\n ''::pg_catalog.char AS attgenerated"); + attgenerated_col = cols++; } if (tableinfo.relkind == RELKIND_INDEX || tableinfo.relkind == RELKIND_PARTITIONED_INDEX) @@ -2011,6 +2018,7 @@ describeOneTableDetails(const char *schemaname, if (show_column_details) { char *identity; + char *generated; char *default_str = ""; printTableAddCell(&cont, PQgetvalue(res, i, attcoll_col), false, false); @@ -2020,16 +2028,19 @@ describeOneTableDetails(const char *schemaname, false, false); identity = PQgetvalue(res, i, attidentity_col); + generated = PQgetvalue(res, i, attgenerated_col); - if (!identity[0]) - /* (note: above we cut off the 'default' string at 128) */ - default_str = PQgetvalue(res, i, attrdef_col); - else if (identity[0] == ATTRIBUTE_IDENTITY_ALWAYS) + if (identity[0] == ATTRIBUTE_IDENTITY_ALWAYS) default_str = "generated always as identity"; else if (identity[0] == ATTRIBUTE_IDENTITY_BY_DEFAULT) default_str = "generated by default as identity"; + else if (generated[0] == ATTRIBUTE_GENERATED_STORED) + default_str = psprintf("generated always as (%s) stored", PQgetvalue(res, i, attrdef_col)); + else + /* (note: above we cut off the 'default' string at 128) */ + default_str = PQgetvalue(res, i, attrdef_col); - printTableAddCell(&cont, default_str, false, false); + printTableAddCell(&cont, default_str, false, generated[0] ? true : false); } /* Info for index columns */ diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h index 66d1b2fc40..a592d22a0e 100644 --- a/src/include/access/tupdesc.h +++ b/src/include/access/tupdesc.h @@ -42,6 +42,7 @@ typedef struct TupleConstr uint16 num_defval; uint16 num_check; bool has_not_null; + bool has_generated_stored; } TupleConstr; /* diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index 85076d0743..83ff373c30 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -28,6 +28,7 @@ typedef struct RawColumnDefault AttrNumber attnum; /* attribute to attach default to */ Node *raw_default; /* default value (untransformed parse tree) */ bool missingMode; /* true if part of add column processing */ + char generated; /* attgenerated setting */ } RawColumnDefault; typedef struct CookedConstraint @@ -120,7 +121,8 @@ extern Node *cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, int32 atttypmod, - const char *attname); + const char *attname, + char attgenerated); extern void DeleteRelationTuple(Oid relid); extern void DeleteAttributeTuples(Oid relid); diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index a6ec122389..04004b5703 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -140,6 +140,9 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(75, /* One of the ATTRIBUTE_IDENTITY_* constants below, or '\0' */ char attidentity BKI_DEFAULT('\0'); + /* One of the ATTRIBUTE_GENERATED_* constants below, or '\0' */ + char attgenerated BKI_DEFAULT('\0'); + /* Is dropped (ie, logically invisible) or not */ bool attisdropped BKI_DEFAULT(f); @@ -201,6 +204,8 @@ typedef FormData_pg_attribute *Form_pg_attribute; #define ATTRIBUTE_IDENTITY_ALWAYS 'a' #define ATTRIBUTE_IDENTITY_BY_DEFAULT 'd' +#define ATTRIBUTE_GENERATED_STORED 's' + #endif /* EXPOSE_TO_CLIENT_CODE */ #endif /* PG_ATTRIBUTE_H */ diff --git a/src/include/catalog/pg_class.dat b/src/include/catalog/pg_class.dat index c89710bc60..9bcf28676d 100644 --- a/src/include/catalog/pg_class.dat +++ b/src/include/catalog/pg_class.dat @@ -34,7 +34,7 @@ relname => 'pg_attribute', reltype => 'pg_attribute', relam => 'heap', relfilenode => '0', relpages => '0', reltuples => '0', relallvisible => '0', reltoastrelid => '0', relhasindex => 'f', relisshared => 'f', - relpersistence => 'p', relkind => 'r', relnatts => '24', relchecks => '0', + relpersistence => 'p', relkind => 'r', relnatts => '25', relchecks => '0', relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f', relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't', relreplident => 'n', relispartition => 'f', relfrozenxid => '3', diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h index b8b289efc0..891b119608 100644 --- a/src/include/executor/nodeModifyTable.h +++ b/src/include/executor/nodeModifyTable.h @@ -15,6 +15,8 @@ #include "nodes/execnodes.h" +extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot); + extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags); extern void ExecEndModifyTable(ModifyTableState *node); extern void ExecReScanModifyTable(ModifyTableState *node); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 62eb1a06ee..4cfae35611 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -452,6 +452,9 @@ typedef struct ResultRelInfo /* array of constraint-checking expr states */ ExprState **ri_ConstraintExprs; + /* array of stored generated columns expr states */ + ExprState **ri_GeneratedExprs; + /* for removing junk attributes from tuples */ JunkFilter *ri_junkFilter; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index fe35783359..0ec2ce924b 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -655,6 +655,7 @@ typedef struct ColumnDef char identity; /* attidentity setting */ RangeVar *identitySequence; /* to store identity sequence name for * ALTER TABLE ... ADD COLUMN */ + char generated; /* attgenerated setting */ CollateClause *collClause; /* untransformed COLLATE spec, if any */ Oid collOid; /* collation OID (InvalidOid if not set) */ List *constraints; /* other constraints on column */ @@ -677,10 +678,11 @@ typedef enum TableLikeOption CREATE_TABLE_LIKE_COMMENTS = 1 << 0, CREATE_TABLE_LIKE_CONSTRAINTS = 1 << 1, CREATE_TABLE_LIKE_DEFAULTS = 1 << 2, - CREATE_TABLE_LIKE_IDENTITY = 1 << 3, - CREATE_TABLE_LIKE_INDEXES = 1 << 4, - CREATE_TABLE_LIKE_STATISTICS = 1 << 5, - CREATE_TABLE_LIKE_STORAGE = 1 << 6, + CREATE_TABLE_LIKE_GENERATED = 1 << 3, + CREATE_TABLE_LIKE_IDENTITY = 1 << 4, + CREATE_TABLE_LIKE_INDEXES = 1 << 5, + CREATE_TABLE_LIKE_STATISTICS = 1 << 6, + CREATE_TABLE_LIKE_STORAGE = 1 << 7, CREATE_TABLE_LIKE_ALL = PG_INT32_MAX } TableLikeOption; @@ -933,6 +935,15 @@ typedef struct PartitionCmd * them in these fields. A whole-row Var reference is represented by * setting the bit for InvalidAttrNumber. * + * updatedCols is also used in some other places, for example, to determine + * which triggers to fire and in FDWs to know which changed columns they + * need to ship off. Generated columns that are caused to be updated by an + * update to a base column are collected in extraUpdatedCols. This is not + * considered for permission checking, but it is useful in those places + * that want to know the full set of columns being updated as opposed to + * only the ones the user explicitly mentioned in the query. (There is + * currently no need for an extraInsertedCols, but it could exist.) + * * securityQuals is a list of security barrier quals (boolean expressions), * to be tested in the listed order before returning a row from the * relation. It is always NIL in parser output. Entries are added by the @@ -1087,6 +1098,7 @@ typedef struct RangeTblEntry Bitmapset *selectedCols; /* columns needing SELECT permission */ Bitmapset *insertedCols; /* columns needing INSERT permission */ Bitmapset *updatedCols; /* columns needing UPDATE permission */ + Bitmapset *extraUpdatedCols; /* generated columns being updated */ List *securityQuals; /* security barrier quals to apply, if any */ } RangeTblEntry; @@ -2086,6 +2098,7 @@ typedef enum ConstrType /* types of constraints */ CONSTR_NOTNULL, CONSTR_DEFAULT, CONSTR_IDENTITY, + CONSTR_GENERATED, CONSTR_CHECK, CONSTR_PRIMARY, CONSTR_UNIQUE, @@ -2124,7 +2137,8 @@ typedef struct Constraint bool is_no_inherit; /* is constraint non-inheritable? */ Node *raw_expr; /* expr, as untransformed parse tree */ char *cooked_expr; /* expr, as nodeToString representation */ - char generated_when; + char generated_when; /* ALWAYS or BY DEFAULT */ + char generated_kind; /* currently always STORED */ /* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */ List *keys; /* String nodes naming referenced key diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index c337f047cb..c556e0f258 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -71,4 +71,6 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node); extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event); +extern bool has_stored_generated_columns(PlannerInfo *root, Index rti); + #endif /* PLANCAT_H */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index f05444008c..00ace8425e 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -383,6 +383,7 @@ PG_KEYWORD("statistics", STATISTICS, UNRESERVED_KEYWORD) PG_KEYWORD("stdin", STDIN, UNRESERVED_KEYWORD) PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD) PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD) +PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD) PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD) PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD) PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD) diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index ea99a0954b..3d8039aa51 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -71,7 +71,8 @@ typedef enum ParseExprKind EXPR_KIND_PARTITION_BOUND, /* partition bound expression */ EXPR_KIND_PARTITION_EXPRESSION, /* PARTITION BY expression */ EXPR_KIND_CALL_ARGUMENT, /* procedure argument in CALL */ - EXPR_KIND_COPY_WHERE /* WHERE condition in COPY FROM */ + EXPR_KIND_COPY_WHERE, /* WHERE condition in COPY FROM */ + EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */ } ParseExprKind; diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 16b0b1d2dc..22a38e189e 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -86,6 +86,7 @@ extern Oid get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype, int16 procnum); extern char *get_attname(Oid relid, AttrNumber attnum, bool missing_ok); extern AttrNumber get_attnum(Oid relid, const char *attname); +extern char get_attgenerated(Oid relid, AttrNumber attnum); extern Oid get_atttype(Oid relid, AttrNumber attnum); extern void get_atttypetypmodcoll(Oid relid, AttrNumber attnum, Oid *typid, int32 *typmod, Oid *collid); diff --git a/src/pl/plperl/expected/plperl_trigger.out b/src/pl/plperl/expected/plperl_trigger.out index 28011cd9f6..d4879e2f03 100644 --- a/src/pl/plperl/expected/plperl_trigger.out +++ b/src/pl/plperl/expected/plperl_trigger.out @@ -6,6 +6,10 @@ CREATE TABLE trigger_test ( v varchar, foo rowcompnest ); +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger LANGUAGE plperl AS $$ # make sure keys are sorted for consistent results - perl no longer @@ -98,6 +102,79 @@ NOTICE: $_TD->{table_name} = 'trigger_test' NOTICE: $_TD->{table_schema} = 'public' NOTICE: $_TD->{when} = 'BEFORE' DROP TRIGGER show_trigger_data_trig on trigger_test; +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +insert into trigger_test_generated (i) values (1); +NOTICE: $_TD->{argc} = '0' +NOTICE: $_TD->{event} = 'INSERT' +NOTICE: $_TD->{level} = 'ROW' +NOTICE: $_TD->{name} = 'show_trigger_data_trig_before' +NOTICE: $_TD->{new} = {'i' => '1'} +NOTICE: $_TD->{relid} = 'bogus:12345' +NOTICE: $_TD->{relname} = 'trigger_test_generated' +NOTICE: $_TD->{table_name} = 'trigger_test_generated' +NOTICE: $_TD->{table_schema} = 'public' +NOTICE: $_TD->{when} = 'BEFORE' +NOTICE: $_TD->{argc} = '0' +NOTICE: $_TD->{event} = 'INSERT' +NOTICE: $_TD->{level} = 'ROW' +NOTICE: $_TD->{name} = 'show_trigger_data_trig_after' +NOTICE: $_TD->{new} = {'i' => '1', 'j' => '2'} +NOTICE: $_TD->{relid} = 'bogus:12345' +NOTICE: $_TD->{relname} = 'trigger_test_generated' +NOTICE: $_TD->{table_name} = 'trigger_test_generated' +NOTICE: $_TD->{table_schema} = 'public' +NOTICE: $_TD->{when} = 'AFTER' +update trigger_test_generated set i = 11 where i = 1; +NOTICE: $_TD->{argc} = '0' +NOTICE: $_TD->{event} = 'UPDATE' +NOTICE: $_TD->{level} = 'ROW' +NOTICE: $_TD->{name} = 'show_trigger_data_trig_before' +NOTICE: $_TD->{new} = {'i' => '11'} +NOTICE: $_TD->{old} = {'i' => '1', 'j' => '2'} +NOTICE: $_TD->{relid} = 'bogus:12345' +NOTICE: $_TD->{relname} = 'trigger_test_generated' +NOTICE: $_TD->{table_name} = 'trigger_test_generated' +NOTICE: $_TD->{table_schema} = 'public' +NOTICE: $_TD->{when} = 'BEFORE' +NOTICE: $_TD->{argc} = '0' +NOTICE: $_TD->{event} = 'UPDATE' +NOTICE: $_TD->{level} = 'ROW' +NOTICE: $_TD->{name} = 'show_trigger_data_trig_after' +NOTICE: $_TD->{new} = {'i' => '11', 'j' => '22'} +NOTICE: $_TD->{old} = {'i' => '1', 'j' => '2'} +NOTICE: $_TD->{relid} = 'bogus:12345' +NOTICE: $_TD->{relname} = 'trigger_test_generated' +NOTICE: $_TD->{table_name} = 'trigger_test_generated' +NOTICE: $_TD->{table_schema} = 'public' +NOTICE: $_TD->{when} = 'AFTER' +delete from trigger_test_generated; +NOTICE: $_TD->{argc} = '0' +NOTICE: $_TD->{event} = 'DELETE' +NOTICE: $_TD->{level} = 'ROW' +NOTICE: $_TD->{name} = 'show_trigger_data_trig_before' +NOTICE: $_TD->{old} = {'i' => '11', 'j' => '22'} +NOTICE: $_TD->{relid} = 'bogus:12345' +NOTICE: $_TD->{relname} = 'trigger_test_generated' +NOTICE: $_TD->{table_name} = 'trigger_test_generated' +NOTICE: $_TD->{table_schema} = 'public' +NOTICE: $_TD->{when} = 'BEFORE' +NOTICE: $_TD->{argc} = '0' +NOTICE: $_TD->{event} = 'DELETE' +NOTICE: $_TD->{level} = 'ROW' +NOTICE: $_TD->{name} = 'show_trigger_data_trig_after' +NOTICE: $_TD->{old} = {'i' => '11', 'j' => '22'} +NOTICE: $_TD->{relid} = 'bogus:12345' +NOTICE: $_TD->{relname} = 'trigger_test_generated' +NOTICE: $_TD->{table_name} = 'trigger_test_generated' +NOTICE: $_TD->{table_schema} = 'public' +NOTICE: $_TD->{when} = 'AFTER' +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; insert into trigger_test values(1,'insert', '("(1)")'); CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; CREATE TRIGGER show_trigger_data_trig @@ -295,3 +372,21 @@ NOTICE: perlsnitch: ddl_command_start DROP TABLE NOTICE: perlsnitch: ddl_command_end DROP TABLE drop event trigger perl_a_snitch; drop event trigger perl_b_snitch; +-- dealing with generated columns +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE plperl +AS $$ +$_TD->{new}{j} = 5; # not allowed +return 'MODIFY'; +$$; +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +ERROR: cannot set generated column "j" +CONTEXT: PL/Perl function "generated_test_func1" +SELECT * FROM trigger_test_generated; + i | j +---+--- +(0 rows) + diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 35d5d121a0..31ba2f262f 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -266,7 +266,7 @@ static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger); -static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc); +static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc, bool include_generated); static SV *plperl_hash_from_datum(Datum attr); static SV *plperl_ref_from_pg_array(Datum arg, Oid typid); static SV *split_array(plperl_array_info *info, int first, int last, int nest); @@ -1644,13 +1644,19 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo) hv_store_string(hv, "name", cstr2sv(tdata->tg_trigger->tgname)); hv_store_string(hv, "relid", cstr2sv(relid)); + /* + * Note: In BEFORE trigger, stored generated columns are not computed yet, + * so don't make them accessible in NEW row. + */ + if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) { event = "INSERT"; if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) hv_store_string(hv, "new", plperl_hash_from_tuple(tdata->tg_trigtuple, - tupdesc)); + tupdesc, + !TRIGGER_FIRED_BEFORE(tdata->tg_event))); } else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) { @@ -1658,7 +1664,8 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo) if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) hv_store_string(hv, "old", plperl_hash_from_tuple(tdata->tg_trigtuple, - tupdesc)); + tupdesc, + true)); } else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) { @@ -1667,10 +1674,12 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo) { hv_store_string(hv, "old", plperl_hash_from_tuple(tdata->tg_trigtuple, - tupdesc)); + tupdesc, + true)); hv_store_string(hv, "new", plperl_hash_from_tuple(tdata->tg_newtuple, - tupdesc)); + tupdesc, + !TRIGGER_FIRED_BEFORE(tdata->tg_event))); } } else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event)) @@ -1791,6 +1800,11 @@ plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot set system attribute \"%s\"", key))); + if (attr->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("cannot set generated column \"%s\"", + key))); modvalues[attn - 1] = plperl_sv_to_datum(val, attr->atttypid, @@ -3012,7 +3026,7 @@ plperl_hash_from_datum(Datum attr) tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; - sv = plperl_hash_from_tuple(&tmptup, tupdesc); + sv = plperl_hash_from_tuple(&tmptup, tupdesc, true); ReleaseTupleDesc(tupdesc); return sv; @@ -3020,7 +3034,7 @@ plperl_hash_from_datum(Datum attr) /* Build a hash from all attributes of a given tuple. */ static SV * -plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc) +plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc, bool include_generated) { dTHX; HV *hv; @@ -3044,6 +3058,13 @@ plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc) if (att->attisdropped) continue; + if (att->attgenerated) + { + /* don't include unless requested */ + if (!include_generated) + continue; + } + attname = NameStr(att->attname); attr = heap_getattr(tuple, i + 1, tupdesc, &isnull); @@ -3198,7 +3219,7 @@ plperl_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 processed, av_extend(rows, processed); for (i = 0; i < processed; i++) { - row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc); + row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc, true); av_push(rows, row); } hv_store_string(result, "rows", @@ -3484,7 +3505,8 @@ plperl_spi_fetchrow(char *cursor) else { row = plperl_hash_from_tuple(SPI_tuptable->vals[0], - SPI_tuptable->tupdesc); + SPI_tuptable->tupdesc, + true); } SPI_freetuptable(SPI_tuptable); } diff --git a/src/pl/plperl/sql/plperl_trigger.sql b/src/pl/plperl/sql/plperl_trigger.sql index 624193b9d0..4adddeb80a 100644 --- a/src/pl/plperl/sql/plperl_trigger.sql +++ b/src/pl/plperl/sql/plperl_trigger.sql @@ -8,6 +8,11 @@ CREATE TABLE trigger_test ( foo rowcompnest ); +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); + CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger LANGUAGE plperl AS $$ # make sure keys are sorted for consistent results - perl no longer @@ -70,6 +75,21 @@ CREATE TRIGGER show_trigger_data_trig DROP TRIGGER show_trigger_data_trig on trigger_test; +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); + +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); + +insert into trigger_test_generated (i) values (1); +update trigger_test_generated set i = 11 where i = 1; +delete from trigger_test_generated; + +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; + insert into trigger_test values(1,'insert', '("(1)")'); CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; @@ -221,3 +241,19 @@ CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test drop event trigger perl_a_snitch; drop event trigger perl_b_snitch; + +-- dealing with generated columns + +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE plperl +AS $$ +$_TD->{new}{j} = 5; # not allowed +return 'MODIFY'; +$$; + +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); + +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +SELECT * FROM trigger_test_generated; diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 6dfcd1611a..17157d9455 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -924,6 +924,26 @@ plpgsql_exec_trigger(PLpgSQL_function *func, false, false); expanded_record_set_tuple(rec_old->erh, trigdata->tg_trigtuple, false, false); + + /* + * In BEFORE trigger, stored generated columns are not computed yet, + * so make them null in the NEW row. (Only needed in UPDATE branch; + * in the INSERT case, they are already null, but in UPDATE, the field + * still contains the old value.) Alternatively, we could construct a + * whole new row structure without the generated columns, but this way + * seems more efficient and potentially less confusing. + */ + if (tupdesc->constr && tupdesc->constr->has_generated_stored && + TRIGGER_FIRED_BEFORE(trigdata->tg_event)) + { + for (int i = 0; i < tupdesc->natts; i++) + if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED) + expanded_record_set_field_internal(rec_new->erh, + i + 1, + (Datum) 0, + true, /*isnull*/ + false, false); + } } else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) { diff --git a/src/pl/plpython/expected/plpython_trigger.out b/src/pl/plpython/expected/plpython_trigger.out index d7ab8ac6b8..742988a5b5 100644 --- a/src/pl/plpython/expected/plpython_trigger.out +++ b/src/pl/plpython/expected/plpython_trigger.out @@ -67,6 +67,10 @@ SELECT * FROM users; -- dump trigger data CREATE TABLE trigger_test (i int, v text ); +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpythonu AS $$ if 'relid' in TD: @@ -203,6 +207,77 @@ NOTICE: TD[when] => BEFORE DROP TRIGGER show_trigger_data_trig_stmt on trigger_test; DROP TRIGGER show_trigger_data_trig_before on trigger_test; DROP TRIGGER show_trigger_data_trig_after on trigger_test; +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +insert into trigger_test_generated (i) values (1); +NOTICE: TD[args] => None +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => {'i': 1} +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => None +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => {'i': 1, 'j': 2} +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +update trigger_test_generated set i = 11 where i = 1; +NOTICE: TD[args] => None +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => {'i': 11} +NOTICE: TD[old] => {'i': 1, 'j': 2} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => None +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => {'i': 11, 'j': 22} +NOTICE: TD[old] => {'i': 1, 'j': 2} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +delete from trigger_test_generated; +NOTICE: TD[args] => None +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => None +NOTICE: TD[old] => {'i': 11, 'j': 22} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => None +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => None +NOTICE: TD[old] => {'i': 11, 'j': 22} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; insert into trigger_test values(1,'insert'); CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; CREATE TRIGGER show_trigger_data_trig @@ -524,3 +599,22 @@ INFO: old: 1 -> a INFO: new: 1 -> b DROP TABLE transition_table_test; DROP FUNCTION transition_table_test_f(); +-- dealing with generated columns +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE plpythonu +AS $$ +TD['new']['j'] = 5 # not allowed +return 'MODIFY' +$$; +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +ERROR: cannot set generated column "j" +CONTEXT: while modifying trigger row +PL/Python function "generated_test_func1" +SELECT * FROM trigger_test_generated; + i | j +---+--- +(0 rows) + diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c index 45ac25b2ae..e4d543a4d4 100644 --- a/src/pl/plpython/plpy_cursorobject.c +++ b/src/pl/plpython/plpy_cursorobject.c @@ -357,7 +357,7 @@ PLy_cursor_iternext(PyObject *self) exec_ctx->curr_proc); ret = PLy_input_from_tuple(&cursor->result, SPI_tuptable->vals[0], - SPI_tuptable->tupdesc); + SPI_tuptable->tupdesc, true); } SPI_freetuptable(SPI_tuptable); @@ -453,7 +453,8 @@ PLy_cursor_fetch(PyObject *self, PyObject *args) { PyObject *row = PLy_input_from_tuple(&cursor->result, SPI_tuptable->vals[i], - SPI_tuptable->tupdesc); + SPI_tuptable->tupdesc, + true); PyList_SetItem(ret->rows, i, row); } diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c index 2137186241..fd6cdc4ce5 100644 --- a/src/pl/plpython/plpy_exec.c +++ b/src/pl/plpython/plpy_exec.c @@ -13,6 +13,7 @@ #include "executor/spi.h" #include "funcapi.h" #include "utils/builtins.h" +#include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/typcache.h" @@ -751,6 +752,11 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r PyDict_SetItemString(pltdata, "level", pltlevel); Py_DECREF(pltlevel); + /* + * Note: In BEFORE trigger, stored generated columns are not computed yet, + * so don't make them accessible in NEW row. + */ + if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) { pltevent = PyString_FromString("INSERT"); @@ -758,7 +764,8 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r PyDict_SetItemString(pltdata, "old", Py_None); pytnew = PLy_input_from_tuple(&proc->result_in, tdata->tg_trigtuple, - rel_descr); + rel_descr, + !TRIGGER_FIRED_BEFORE(tdata->tg_event)); PyDict_SetItemString(pltdata, "new", pytnew); Py_DECREF(pytnew); *rv = tdata->tg_trigtuple; @@ -770,7 +777,8 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r PyDict_SetItemString(pltdata, "new", Py_None); pytold = PLy_input_from_tuple(&proc->result_in, tdata->tg_trigtuple, - rel_descr); + rel_descr, + true); PyDict_SetItemString(pltdata, "old", pytold); Py_DECREF(pytold); *rv = tdata->tg_trigtuple; @@ -781,12 +789,14 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r pytnew = PLy_input_from_tuple(&proc->result_in, tdata->tg_newtuple, - rel_descr); + rel_descr, + !TRIGGER_FIRED_BEFORE(tdata->tg_event)); PyDict_SetItemString(pltdata, "new", pytnew); Py_DECREF(pytnew); pytold = PLy_input_from_tuple(&proc->result_in, tdata->tg_trigtuple, - rel_descr); + rel_descr, + true); PyDict_SetItemString(pltdata, "old", pytold); Py_DECREF(pytold); *rv = tdata->tg_newtuple; @@ -952,6 +962,11 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot set system attribute \"%s\"", plattstr))); + if (TupleDescAttr(tupdesc, attn - 1)->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("cannot set generated column \"%s\"", + plattstr))); plval = PyDict_GetItem(plntup, platt); if (plval == NULL) diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c index 41155fc81e..fb23a7b3a4 100644 --- a/src/pl/plpython/plpy_spi.c +++ b/src/pl/plpython/plpy_spi.c @@ -419,7 +419,8 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status) { PyObject *row = PLy_input_from_tuple(&ininfo, tuptable->vals[i], - tuptable->tupdesc); + tuptable->tupdesc, + true); PyList_SetItem(result->rows, i, row); } diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c index d6a6a849c3..6365e461e9 100644 --- a/src/pl/plpython/plpy_typeio.c +++ b/src/pl/plpython/plpy_typeio.c @@ -41,7 +41,7 @@ static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d); static PyObject *PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim, char **dataptr_p, bits8 **bitmap_p, int *bitmask_p); static PyObject *PLyDict_FromComposite(PLyDatumToOb *arg, Datum d); -static PyObject *PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc); +static PyObject *PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated); /* conversion from Python objects to Datums */ static Datum PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv, @@ -134,7 +134,7 @@ PLy_output_convert(PLyObToDatum *arg, PyObject *val, bool *isnull) * but in practice all callers have the right tupdesc available. */ PyObject * -PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc) +PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated) { PyObject *dict; PLyExecutionContext *exec_ctx = PLy_current_execution_context(); @@ -148,7 +148,7 @@ PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc) oldcontext = MemoryContextSwitchTo(scratch_context); - dict = PLyDict_FromTuple(arg, tuple, desc); + dict = PLyDict_FromTuple(arg, tuple, desc, include_generated); MemoryContextSwitchTo(oldcontext); @@ -804,7 +804,7 @@ PLyDict_FromComposite(PLyDatumToOb *arg, Datum d) tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; - dict = PLyDict_FromTuple(arg, &tmptup, tupdesc); + dict = PLyDict_FromTuple(arg, &tmptup, tupdesc, true); ReleaseTupleDesc(tupdesc); @@ -815,7 +815,7 @@ PLyDict_FromComposite(PLyDatumToOb *arg, Datum d) * Transform a tuple into a Python dict object. */ static PyObject * -PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc) +PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated) { PyObject *volatile dict; @@ -842,6 +842,13 @@ PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc) if (attr->attisdropped) continue; + if (attr->attgenerated) + { + /* don't include unless requested */ + if (!include_generated) + continue; + } + key = NameStr(attr->attname); vattr = heap_getattr(tuple, (i + 1), desc, &is_null); diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h index 82bdfae548..f210178238 100644 --- a/src/pl/plpython/plpy_typeio.h +++ b/src/pl/plpython/plpy_typeio.h @@ -151,7 +151,7 @@ extern Datum PLy_output_convert(PLyObToDatum *arg, PyObject *val, bool *isnull); extern PyObject *PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, - TupleDesc desc); + TupleDesc desc, bool include_generated); extern void PLy_input_setup_func(PLyDatumToOb *arg, MemoryContext arg_mcxt, Oid typeOid, int32 typmod, diff --git a/src/pl/plpython/sql/plpython_trigger.sql b/src/pl/plpython/sql/plpython_trigger.sql index 79c24b714b..19852dc585 100644 --- a/src/pl/plpython/sql/plpython_trigger.sql +++ b/src/pl/plpython/sql/plpython_trigger.sql @@ -67,6 +67,11 @@ CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW CREATE TABLE trigger_test (i int, v text ); +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); + CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpythonu AS $$ if 'relid' in TD: @@ -109,6 +114,21 @@ CREATE TRIGGER show_trigger_data_trig_stmt DROP TRIGGER show_trigger_data_trig_before on trigger_test; DROP TRIGGER show_trigger_data_trig_after on trigger_test; +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); + +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); + +insert into trigger_test_generated (i) values (1); +update trigger_test_generated set i = 11 where i = 1; +delete from trigger_test_generated; + +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; + insert into trigger_test values(1,'insert'); CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; @@ -430,3 +450,20 @@ CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test DROP TABLE transition_table_test; DROP FUNCTION transition_table_test_f(); + + +-- dealing with generated columns + +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE plpythonu +AS $$ +TD['new']['j'] = 5 # not allowed +return 'MODIFY' +$$; + +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); + +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +SELECT * FROM trigger_test_generated; diff --git a/src/pl/tcl/expected/pltcl_trigger.out b/src/pl/tcl/expected/pltcl_trigger.out index 2d5daedc11..008ea19509 100644 --- a/src/pl/tcl/expected/pltcl_trigger.out +++ b/src/pl/tcl/expected/pltcl_trigger.out @@ -61,6 +61,10 @@ CREATE TABLE trigger_test ( ); -- Make certain dropped attributes are handled correctly ALTER TABLE trigger_test DROP dropme; +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); CREATE VIEW trigger_test_view AS SELECT i, v FROM trigger_test; CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$ if {$TG_table_name eq "trigger_test" && $TG_level eq "ROW" && $TG_op ne "DELETE"} { @@ -112,6 +116,12 @@ FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); CREATE TRIGGER statement_trigger BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(42,'statement trigger'); +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); CREATE TRIGGER show_trigger_data_view_trig INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view'); @@ -631,6 +641,75 @@ NOTICE: TG_table_name: trigger_test NOTICE: TG_table_schema: public NOTICE: TG_when: BEFORE NOTICE: args: {23 skidoo} +insert into trigger_test_generated (i) values (1); +NOTICE: NEW: {i: 1} +NOTICE: OLD: {} +NOTICE: TG_level: ROW +NOTICE: TG_name: show_trigger_data_trig_before +NOTICE: TG_op: INSERT +NOTICE: TG_relatts: {{} i j} +NOTICE: TG_relid: bogus:12345 +NOTICE: TG_table_name: trigger_test_generated +NOTICE: TG_table_schema: public +NOTICE: TG_when: BEFORE +NOTICE: args: {} +NOTICE: NEW: {i: 1, j: 2} +NOTICE: OLD: {} +NOTICE: TG_level: ROW +NOTICE: TG_name: show_trigger_data_trig_after +NOTICE: TG_op: INSERT +NOTICE: TG_relatts: {{} i j} +NOTICE: TG_relid: bogus:12345 +NOTICE: TG_table_name: trigger_test_generated +NOTICE: TG_table_schema: public +NOTICE: TG_when: AFTER +NOTICE: args: {} +update trigger_test_generated set i = 11 where i = 1; +NOTICE: NEW: {i: 11} +NOTICE: OLD: {i: 1, j: 2} +NOTICE: TG_level: ROW +NOTICE: TG_name: show_trigger_data_trig_before +NOTICE: TG_op: UPDATE +NOTICE: TG_relatts: {{} i j} +NOTICE: TG_relid: bogus:12345 +NOTICE: TG_table_name: trigger_test_generated +NOTICE: TG_table_schema: public +NOTICE: TG_when: BEFORE +NOTICE: args: {} +NOTICE: NEW: {i: 11, j: 22} +NOTICE: OLD: {i: 1, j: 2} +NOTICE: TG_level: ROW +NOTICE: TG_name: show_trigger_data_trig_after +NOTICE: TG_op: UPDATE +NOTICE: TG_relatts: {{} i j} +NOTICE: TG_relid: bogus:12345 +NOTICE: TG_table_name: trigger_test_generated +NOTICE: TG_table_schema: public +NOTICE: TG_when: AFTER +NOTICE: args: {} +delete from trigger_test_generated; +NOTICE: NEW: {} +NOTICE: OLD: {i: 11, j: 22} +NOTICE: TG_level: ROW +NOTICE: TG_name: show_trigger_data_trig_before +NOTICE: TG_op: DELETE +NOTICE: TG_relatts: {{} i j} +NOTICE: TG_relid: bogus:12345 +NOTICE: TG_table_name: trigger_test_generated +NOTICE: TG_table_schema: public +NOTICE: TG_when: BEFORE +NOTICE: args: {} +NOTICE: NEW: {} +NOTICE: OLD: {i: 11, j: 22} +NOTICE: TG_level: ROW +NOTICE: TG_name: show_trigger_data_trig_after +NOTICE: TG_op: DELETE +NOTICE: TG_relatts: {{} i j} +NOTICE: TG_relid: bogus:12345 +NOTICE: TG_table_name: trigger_test_generated +NOTICE: TG_table_schema: public +NOTICE: TG_when: AFTER +NOTICE: args: {} insert into trigger_test_view values(2,'insert'); NOTICE: NEW: {i: 2, v: insert} NOTICE: OLD: {} @@ -738,6 +817,8 @@ NOTICE: TG_table_name: trigger_test NOTICE: TG_table_schema: public NOTICE: TG_when: BEFORE NOTICE: args: {42 {statement trigger}} +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; -- should error insert into trigger_test(test_argisnull) values(true); NOTICE: NEW: {} @@ -787,3 +868,21 @@ INFO: old: 1 -> a INFO: new: 1 -> b drop table transition_table_test; drop function transition_table_test_f(); +-- dealing with generated columns +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE pltcl +AS $$ +# not allowed +set NEW(j) 5 +return [array get NEW] +$$; +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +ERROR: cannot set generated column "j" +SELECT * FROM trigger_test_generated; + i | j +---+--- +(0 rows) + diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index 76c9afc339..1362ca51d1 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -324,7 +324,7 @@ static void pltcl_subtrans_abort(Tcl_Interp *interp, static void pltcl_set_tuple_values(Tcl_Interp *interp, const char *arrayname, uint64 tupno, HeapTuple tuple, TupleDesc tupdesc); -static Tcl_Obj *pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc); +static Tcl_Obj *pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc, bool include_generated); static HeapTuple pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc, pltcl_call_state *call_state); @@ -889,7 +889,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state, tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; - list_tmp = pltcl_build_tuple_argument(&tmptup, tupdesc); + list_tmp = pltcl_build_tuple_argument(&tmptup, tupdesc, true); Tcl_ListObjAppendElement(NULL, tcl_cmd, list_tmp); ReleaseTupleDesc(tupdesc); @@ -1060,7 +1060,6 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state, volatile HeapTuple rettup; Tcl_Obj *tcl_cmd; Tcl_Obj *tcl_trigtup; - Tcl_Obj *tcl_newtup; int tcl_rc; int i; const char *result; @@ -1162,20 +1161,22 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state, Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewStringObj("ROW", -1)); - /* Build the data list for the trigtuple */ - tcl_trigtup = pltcl_build_tuple_argument(trigdata->tg_trigtuple, - tupdesc); - /* * Now the command part of the event for TG_op and data for NEW * and OLD + * + * Note: In BEFORE trigger, stored generated columns are not computed yet, + * so don't make them accessible in NEW row. */ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) { Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewStringObj("INSERT", -1)); - Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup); + Tcl_ListObjAppendElement(NULL, tcl_cmd, + pltcl_build_tuple_argument(trigdata->tg_trigtuple, + tupdesc, + !TRIGGER_FIRED_BEFORE(trigdata->tg_event))); Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj()); rettup = trigdata->tg_trigtuple; @@ -1186,7 +1187,10 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state, Tcl_NewStringObj("DELETE", -1)); Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj()); - Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup); + Tcl_ListObjAppendElement(NULL, tcl_cmd, + pltcl_build_tuple_argument(trigdata->tg_trigtuple, + tupdesc, + true)); rettup = trigdata->tg_trigtuple; } @@ -1195,11 +1199,14 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state, Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewStringObj("UPDATE", -1)); - tcl_newtup = pltcl_build_tuple_argument(trigdata->tg_newtuple, - tupdesc); - - Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_newtup); - Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup); + Tcl_ListObjAppendElement(NULL, tcl_cmd, + pltcl_build_tuple_argument(trigdata->tg_newtuple, + tupdesc, + !TRIGGER_FIRED_BEFORE(trigdata->tg_event))); + Tcl_ListObjAppendElement(NULL, tcl_cmd, + pltcl_build_tuple_argument(trigdata->tg_trigtuple, + tupdesc, + true)); rettup = trigdata->tg_newtuple; } @@ -3091,7 +3098,7 @@ pltcl_set_tuple_values(Tcl_Interp *interp, const char *arrayname, * from all attributes of a given tuple **********************************************************************/ static Tcl_Obj * -pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc) +pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc, bool include_generated) { Tcl_Obj *retobj = Tcl_NewObj(); int i; @@ -3110,6 +3117,13 @@ pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc) if (att->attisdropped) continue; + if (att->attgenerated) + { + /* don't include unless requested */ + if (!include_generated) + continue; + } + /************************************************************ * Get the attribute name ************************************************************/ @@ -3219,6 +3233,12 @@ pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc, errmsg("cannot set system attribute \"%s\"", fieldName))); + if (TupleDescAttr(tupdesc, attn - 1)->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("cannot set generated column \"%s\"", + fieldName))); + values[attn - 1] = utf_u2e(Tcl_GetString(kvObjv[i + 1])); } diff --git a/src/pl/tcl/sql/pltcl_trigger.sql b/src/pl/tcl/sql/pltcl_trigger.sql index 277d9a0413..2db75a333a 100644 --- a/src/pl/tcl/sql/pltcl_trigger.sql +++ b/src/pl/tcl/sql/pltcl_trigger.sql @@ -71,6 +71,11 @@ CREATE TABLE trigger_test ( -- Make certain dropped attributes are handled correctly ALTER TABLE trigger_test DROP dropme; +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); + CREATE VIEW trigger_test_view AS SELECT i, v FROM trigger_test; CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$ @@ -125,6 +130,13 @@ CREATE TRIGGER statement_trigger BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(42,'statement trigger'); +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); + CREATE TRIGGER show_trigger_data_view_trig INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view'); @@ -531,6 +543,10 @@ CREATE TRIGGER show_trigger_data_view_trig -- show dump of trigger data insert into trigger_test values(1,'insert'); +insert into trigger_test_generated (i) values (1); +update trigger_test_generated set i = 11 where i = 1; +delete from trigger_test_generated; + insert into trigger_test_view values(2,'insert'); update trigger_test_view set v = 'update' where i=1; delete from trigger_test_view; @@ -540,6 +556,9 @@ CREATE TRIGGER show_trigger_data_view_trig delete from trigger_test; truncate trigger_test; +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; + -- should error insert into trigger_test(test_argisnull) values(true); @@ -565,3 +584,20 @@ CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test update transition_table_test set name = 'b'; drop table transition_table_test; drop function transition_table_test_f(); + +-- dealing with generated columns + +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE pltcl +AS $$ +# not allowed +set NEW(j) 5 +return [array get NEW] +$$; + +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); + +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +SELECT * FROM trigger_test_generated; diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out index b582211270..31db405175 100644 --- a/src/test/regress/expected/create_table_like.out +++ b/src/test/regress/expected/create_table_like.out @@ -113,6 +113,52 @@ SELECT * FROM test_like_id_3; -- identity was copied and applied (1 row) DROP TABLE test_like_id_1, test_like_id_2, test_like_id_3; +CREATE TABLE test_like_gen_1 (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +\d test_like_gen_1 + Table "public.test_like_gen_1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | | + b | integer | | | generated always as (a * 2) stored + +INSERT INTO test_like_gen_1 (a) VALUES (1); +SELECT * FROM test_like_gen_1; + a | b +---+--- + 1 | 2 +(1 row) + +CREATE TABLE test_like_gen_2 (LIKE test_like_gen_1); +\d test_like_gen_2 + Table "public.test_like_gen_2" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | + +INSERT INTO test_like_gen_2 (a) VALUES (1); +SELECT * FROM test_like_gen_2; + a | b +---+--- + 1 | +(1 row) + +CREATE TABLE test_like_gen_3 (LIKE test_like_gen_1 INCLUDING GENERATED); +\d test_like_gen_3 + Table "public.test_like_gen_3" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | | + b | integer | | | generated always as (a * 2) stored + +INSERT INTO test_like_gen_3 (a) VALUES (1); +SELECT * FROM test_like_gen_3; + a | b +---+--- + 1 | 2 +(1 row) + +DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3; CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */ INSERT INTO inhg VALUES (5, 10); INSERT INTO inhg VALUES (20, 10); -- should fail diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out new file mode 100644 index 0000000000..ea1470d40e --- /dev/null +++ b/src/test/regress/expected/generated.out @@ -0,0 +1,739 @@ +-- sanity check of system catalog +SELECT attrelid, attname, attgenerated FROM pg_attribute WHERE attgenerated NOT IN ('', 's'); + attrelid | attname | attgenerated +----------+---------+-------------- +(0 rows) + +CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55) STORED); +CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +SELECT table_name, column_name, column_default, is_nullable, is_generated, generation_expression FROM information_schema.columns WHERE table_name LIKE 'gtest_' ORDER BY 1, 2; + table_name | column_name | column_default | is_nullable | is_generated | generation_expression +------------+-------------+----------------+-------------+--------------+----------------------- + gtest0 | a | | NO | NEVER | + gtest0 | b | | YES | ALWAYS | 55 + gtest1 | a | | NO | NEVER | + gtest1 | b | | YES | ALWAYS | (a * 2) +(4 rows) + +SELECT table_name, column_name, dependent_column FROM information_schema.column_column_usage ORDER BY 1, 2, 3; + table_name | column_name | dependent_column +------------+-------------+------------------ + gtest1 | a | b +(1 row) + +\d gtest1 + Table "public.gtest1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | not null | + b | integer | | | generated always as (a * 2) stored +Indexes: + "gtest1_pkey" PRIMARY KEY, btree (a) + +-- duplicate generated +CREATE TABLE gtest_err_1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ALWAYS AS (a * 3) STORED); +ERROR: multiple generation clauses specified for column "b" of table "gtest_err_1" +LINE 1: ...ARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ... + ^ +-- references to other generated columns, including self-references +CREATE TABLE gtest_err_2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STORED); +ERROR: cannot use generated column "b" in column generation expression +LINE 1: ...2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STO... + ^ +DETAIL: A generated column cannot reference another generated column. +CREATE TABLE gtest_err_2b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STORED); +ERROR: cannot use generated column "b" in column generation expression +LINE 1: ...AYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STO... + ^ +DETAIL: A generated column cannot reference another generated column. +-- invalid reference +CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STORED); +ERROR: column "c" does not exist +LINE 1: ..._3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STO... + ^ +-- generation expression must be immutable +CREATE TABLE gtest_err_4 (a int PRIMARY KEY, b double precision GENERATED ALWAYS AS (random()) STORED); +ERROR: generation expression is not immutable +-- cannot have default/identity and generated +CREATE TABLE gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ALWAYS AS (a * 2) STORED); +ERROR: both default and generation expression specified for column "b" of table "gtest_err_5a" +LINE 1: ... gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ... + ^ +CREATE TABLE gtest_err_5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ALWAYS AS (a * 2) STORED); +ERROR: both identity and generation expression specified for column "b" of table "gtest_err_5b" +LINE 1: ...t PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ... + ^ +-- reference to system column not allowed in generated column +CREATE TABLE gtest_err_6a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37) STORED); +ERROR: cannot use system column "xmin" in column generation expression +LINE 1: ...a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37... + ^ +INSERT INTO gtest1 VALUES (1); +INSERT INTO gtest1 VALUES (2, DEFAULT); +INSERT INTO gtest1 VALUES (3, 33); -- error +ERROR: cannot insert into column "b" +DETAIL: Column "b" is a generated column. +SELECT * FROM gtest1 ORDER BY a; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +UPDATE gtest1 SET b = DEFAULT WHERE a = 1; +UPDATE gtest1 SET b = 11 WHERE a = 1; -- error +ERROR: column "b" can only be updated to DEFAULT +DETAIL: Column "b" is a generated column. +SELECT * FROM gtest1 ORDER BY a; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +SELECT a, b, b * 2 AS b2 FROM gtest1 ORDER BY a; + a | b | b2 +---+---+---- + 1 | 2 | 4 + 2 | 4 | 8 +(2 rows) + +SELECT a, b FROM gtest1 WHERE b = 4 ORDER BY a; + a | b +---+--- + 2 | 4 +(1 row) + +-- test that overflow error happens on write +INSERT INTO gtest1 VALUES (2000000000); +ERROR: integer out of range +SELECT * FROM gtest1; + a | b +---+--- + 2 | 4 + 1 | 2 +(2 rows) + +DELETE FROM gtest1 WHERE a = 2000000000; +-- test with joins +CREATE TABLE gtestx (x int, y int); +INSERT INTO gtestx VALUES (11, 1), (22, 2), (33, 3); +SELECT * FROM gtestx, gtest1 WHERE gtestx.y = gtest1.a; + x | y | a | b +----+---+---+--- + 11 | 1 | 1 | 2 + 22 | 2 | 2 | 4 +(2 rows) + +DROP TABLE gtestx; +-- test UPDATE/DELETE quals +SELECT * FROM gtest1 ORDER BY a; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +UPDATE gtest1 SET a = 3 WHERE b = 4; +SELECT * FROM gtest1 ORDER BY a; + a | b +---+--- + 1 | 2 + 3 | 6 +(2 rows) + +DELETE FROM gtest1 WHERE b = 2; +SELECT * FROM gtest1 ORDER BY a; + a | b +---+--- + 3 | 6 +(1 row) + +-- views +CREATE VIEW gtest1v AS SELECT * FROM gtest1; +SELECT * FROM gtest1v; + a | b +---+--- + 3 | 6 +(1 row) + +INSERT INTO gtest1v VALUES (4, 8); -- fails +ERROR: cannot insert into column "b" +DETAIL: Column "b" is a generated column. +DROP VIEW gtest1v; +-- CTEs +WITH foo AS (SELECT * FROM gtest1) SELECT * FROM foo; + a | b +---+--- + 3 | 6 +(1 row) + +-- inheritance +CREATE TABLE gtest1_1 () INHERITS (gtest1); +SELECT * FROM gtest1_1; + a | b +---+--- +(0 rows) + +\d gtest1_1 + Table "public.gtest1_1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | not null | + b | integer | | | generated always as (a * 2) stored +Inherits: gtest1 + +INSERT INTO gtest1_1 VALUES (4); +SELECT * FROM gtest1_1; + a | b +---+--- + 4 | 8 +(1 row) + +SELECT * FROM gtest1; + a | b +---+--- + 3 | 6 + 4 | 8 +(2 rows) + +-- test inheritance mismatch +CREATE TABLE gtesty (x int, b int); +CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty); -- error +NOTICE: merging multiple inherited definitions of column "b" +ERROR: inherited column "b" has a generation conflict +DROP TABLE gtesty; +-- test stored update +CREATE TABLE gtest3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 3) STORED); +INSERT INTO gtest3 (a) VALUES (1), (2), (3); +SELECT * FROM gtest3 ORDER BY a; + a | b +---+--- + 1 | 3 + 2 | 6 + 3 | 9 +(3 rows) + +UPDATE gtest3 SET a = 22 WHERE a = 2; +SELECT * FROM gtest3 ORDER BY a; + a | b +----+---- + 1 | 3 + 3 | 9 + 22 | 66 +(3 rows) + +-- COPY +TRUNCATE gtest1; +INSERT INTO gtest1 (a) VALUES (1), (2); +COPY gtest1 TO stdout; +1 +2 +COPY gtest1 (a, b) TO stdout; +ERROR: column "b" is a generated column +DETAIL: Generated columns cannot be used in COPY. +COPY gtest1 FROM stdin; +COPY gtest1 (a, b) FROM stdin; +ERROR: column "b" is a generated column +DETAIL: Generated columns cannot be used in COPY. +SELECT * FROM gtest1 ORDER BY a; + a | b +---+--- + 1 | 2 + 2 | 4 + 3 | 6 + 4 | 8 +(4 rows) + +TRUNCATE gtest3; +INSERT INTO gtest3 (a) VALUES (1), (2); +COPY gtest3 TO stdout; +1 +2 +COPY gtest3 (a, b) TO stdout; +ERROR: column "b" is a generated column +DETAIL: Generated columns cannot be used in COPY. +COPY gtest3 FROM stdin; +COPY gtest3 (a, b) FROM stdin; +ERROR: column "b" is a generated column +DETAIL: Generated columns cannot be used in COPY. +SELECT * FROM gtest3 ORDER BY a; + a | b +---+---- + 1 | 3 + 2 | 6 + 3 | 9 + 4 | 12 +(4 rows) + +-- null values +CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) STORED); +INSERT INTO gtest2 VALUES (1); +SELECT * FROM gtest2; + a | b +---+--- + 1 | +(1 row) + +-- composite types +CREATE TYPE double_int as (a int, b int); +CREATE TABLE gtest4 ( + a int, + b double_int GENERATED ALWAYS AS ((a * 2, a * 3)) STORED +); +INSERT INTO gtest4 VALUES (1), (6); +SELECT * FROM gtest4; + a | b +---+--------- + 1 | (2,3) + 6 | (12,18) +(2 rows) + +DROP TABLE gtest4; +DROP TYPE double_int; +-- using tableoid is allowed +CREATE TABLE gtest_tableoid ( + a int PRIMARY KEY, + b bool GENERATED ALWAYS AS (tableoid <> 0) STORED +); +INSERT INTO gtest_tableoid VALUES (1), (2); +SELECT * FROM gtest_tableoid; + a | b +---+--- + 1 | t + 2 | t +(2 rows) + +-- drop column behavior +CREATE TABLE gtest10 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED); +ALTER TABLE gtest10 DROP COLUMN b; +\d gtest10 + Table "public.gtest10" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | not null | +Indexes: + "gtest10_pkey" PRIMARY KEY, btree (a) + +-- privileges +CREATE USER regress_user11; +CREATE TABLE gtest11s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED); +INSERT INTO gtest11s VALUES (1, 10), (2, 20); +GRANT SELECT (a, c) ON gtest11s TO regress_user11; +CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL; +REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC; +CREATE TABLE gtest12s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) STORED); +INSERT INTO gtest12s VALUES (1, 10), (2, 20); +GRANT SELECT (a, c) ON gtest12s TO regress_user11; +SET ROLE regress_user11; +SELECT a, b FROM gtest11s; -- not allowed +ERROR: permission denied for table gtest11s +SELECT a, c FROM gtest11s; -- allowed + a | c +---+---- + 1 | 20 + 2 | 40 +(2 rows) + +SELECT gf1(10); -- not allowed +ERROR: permission denied for function gf1 +SELECT a, c FROM gtest12s; -- allowed + a | c +---+---- + 1 | 30 + 2 | 60 +(2 rows) + +RESET ROLE; +DROP TABLE gtest11s, gtest12s; +DROP FUNCTION gf1(int); +DROP USER regress_user11; +-- check constraints +CREATE TABLE gtest20 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED CHECK (b < 50)); +INSERT INTO gtest20 (a) VALUES (10); -- ok +INSERT INTO gtest20 (a) VALUES (30); -- violates constraint +ERROR: new row for relation "gtest20" violates check constraint "gtest20_b_check" +DETAIL: Failing row contains (30, 60). +CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest20a (a) VALUES (10); +INSERT INTO gtest20a (a) VALUES (30); +ALTER TABLE gtest20a ADD CHECK (b < 50); -- fails on existing row +ERROR: check constraint "gtest20a_b_check" is violated by some row +CREATE TABLE gtest20b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest20b (a) VALUES (10); +INSERT INTO gtest20b (a) VALUES (30); +ALTER TABLE gtest20b ADD CONSTRAINT chk CHECK (b < 50) NOT VALID; +ALTER TABLE gtest20b VALIDATE CONSTRAINT chk; -- fails on existing row +ERROR: check constraint "chk" is violated by some row +-- not-null constraints +CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED NOT NULL); +INSERT INTO gtest21a (a) VALUES (1); -- ok +INSERT INTO gtest21a (a) VALUES (0); -- violates constraint +ERROR: null value in column "b" violates not-null constraint +DETAIL: Failing row contains (0, null). +CREATE TABLE gtest21b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED); +ALTER TABLE gtest21b ALTER COLUMN b SET NOT NULL; +INSERT INTO gtest21b (a) VALUES (1); -- ok +INSERT INTO gtest21b (a) VALUES (0); -- violates constraint +ERROR: null value in column "b" violates not-null constraint +DETAIL: Failing row contains (0, null). +ALTER TABLE gtest21b ALTER COLUMN b DROP NOT NULL; +INSERT INTO gtest21b (a) VALUES (0); -- ok now +-- index constraints +CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED UNIQUE); +CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a * 2) STORED, PRIMARY KEY (a, b)); +-- indexes +CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +CREATE INDEX gtest22c_b_idx ON gtest22c (b); +CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); +CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; +\d gtest22c + Table "public.gtest22c" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | | + b | integer | | | generated always as (a * 2) stored +Indexes: + "gtest22c_b_idx" btree (b) + "gtest22c_expr_idx" btree ((b * 3)) + "gtest22c_pred_idx" btree (a) WHERE b > 0 + +INSERT INTO gtest22c VALUES (1), (2), (3); +SET enable_seqscan TO off; +SET enable_bitmapscan TO off; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4; + QUERY PLAN +--------------------------------------------- + Index Scan using gtest22c_b_idx on gtest22c + Index Cond: (b = 4) +(2 rows) + +SELECT * FROM gtest22c WHERE b = 4; + a | b +---+--- + 2 | 4 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6; + QUERY PLAN +------------------------------------------------ + Index Scan using gtest22c_expr_idx on gtest22c + Index Cond: ((b * 3) = 6) +(2 rows) + +SELECT * FROM gtest22c WHERE b * 3 = 6; + a | b +---+--- + 1 | 2 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; + QUERY PLAN +------------------------------------------------ + Index Scan using gtest22c_pred_idx on gtest22c + Index Cond: (a = 1) +(2 rows) + +SELECT * FROM gtest22c WHERE a = 1 AND b > 0; + a | b +---+--- + 1 | 2 +(1 row) + +RESET enable_seqscan; +RESET enable_bitmapscan; +-- foreign keys +CREATE TABLE gtest23a (x int PRIMARY KEY, y int); +INSERT INTO gtest23a VALUES (1, 11), (2, 22), (3, 33); +CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON UPDATE CASCADE); -- error +ERROR: invalid ON UPDATE action for foreign key constraint containing generated column +CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON DELETE SET NULL); -- error +ERROR: invalid ON DELETE action for foreign key constraint containing generated column +CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x)); +\d gtest23b + Table "public.gtest23b" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | not null | + b | integer | | | generated always as (a * 2) stored +Indexes: + "gtest23b_pkey" PRIMARY KEY, btree (a) +Foreign-key constraints: + "gtest23b_b_fkey" FOREIGN KEY (b) REFERENCES gtest23a(x) + +INSERT INTO gtest23b VALUES (1); -- ok +INSERT INTO gtest23b VALUES (5); -- error +ERROR: insert or update on table "gtest23b" violates foreign key constraint "gtest23b_b_fkey" +DETAIL: Key (b)=(10) is not present in table "gtest23a". +DROP TABLE gtest23b; +DROP TABLE gtest23a; +CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) STORED, PRIMARY KEY (y)); +INSERT INTO gtest23p VALUES (1), (2), (3); +CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y)); +INSERT INTO gtest23q VALUES (1, 2); -- ok +INSERT INTO gtest23q VALUES (2, 5); -- error +ERROR: insert or update on table "gtest23q" violates foreign key constraint "gtest23q_b_fkey" +DETAIL: Key (b)=(5) is not present in table "gtest23p". +-- domains +CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10); +CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest24 (a) VALUES (4); -- ok +INSERT INTO gtest24 (a) VALUES (6); -- error +ERROR: value for domain gtestdomain1 violates check constraint "gtestdomain1_check" +-- typed tables (currently not supported) +CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint); +CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) STORED); +ERROR: generated columns are not supported on typed tables +DROP TYPE gtest_type CASCADE; +-- table partitions (currently not supported) +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); +CREATE TABLE gtest_child PARTITION OF gtest_parent ( + f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 2) STORED +) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error +ERROR: generated columns are not supported on partitions +DROP TABLE gtest_parent; +-- partitioned table +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f1); +CREATE TABLE gtest_child PARTITION OF gtest_parent FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1); +SELECT * FROM gtest_parent; + f1 | f2 | f3 +------------+----+---- + 07-15-2016 | 1 | 2 +(1 row) + +SELECT * FROM gtest_child; + f1 | f2 | f3 +------------+----+---- + 07-15-2016 | 1 | 2 +(1 row) + +DROP TABLE gtest_parent; +-- generated columns in partition key (not allowed) +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); +ERROR: cannot use generated column in partition key +LINE 1: ...ENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); + ^ +DETAIL: Column "f3" is a generated column. +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); +ERROR: cannot use generated column in partition key +LINE 1: ...ED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); + ^ +DETAIL: Column "f3" is a generated column. +-- ALTER TABLE ... ADD COLUMN +CREATE TABLE gtest25 (a int PRIMARY KEY); +INSERT INTO gtest25 VALUES (3), (4); +ALTER TABLE gtest25 ADD COLUMN b int GENERATED ALWAYS AS (a * 3) STORED; +SELECT * FROM gtest25 ORDER BY a; + a | b +---+---- + 3 | 9 + 4 | 12 +(2 rows) + +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4) STORED; -- error +ERROR: cannot use generated column "b" in column generation expression +DETAIL: A generated column cannot reference another generated column. +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error +ERROR: column "z" does not exist +-- ALTER TABLE ... ALTER COLUMN +CREATE TABLE gtest27 ( + a int, + b int GENERATED ALWAYS AS (a * 2) STORED +); +INSERT INTO gtest27 (a) VALUES (3), (4); +ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error +ERROR: cannot alter type of a column used by a generated column +DETAIL: Column "a" is used by generated column "b". +ALTER TABLE gtest27 ALTER COLUMN b TYPE numeric; +\d gtest27 + Table "public.gtest27" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+-------------------------------------- + a | integer | | | + b | numeric | | | generated always as ((a * 2)) stored + +SELECT * FROM gtest27; + a | b +---+--- + 3 | 6 + 4 | 8 +(2 rows) + +ALTER TABLE gtest27 ALTER COLUMN b TYPE boolean USING b <> 0; -- error +ERROR: generation expression for column "b" cannot be cast automatically to type boolean +ALTER TABLE gtest27 ALTER COLUMN b DROP DEFAULT; -- error +ERROR: column "b" of relation "gtest27" is a generated column +\d gtest27 + Table "public.gtest27" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+-------------------------------------- + a | integer | | | + b | numeric | | | generated always as ((a * 2)) stored + +-- triggers +CREATE TABLE gtest26 ( + a int PRIMARY KEY, + b int GENERATED ALWAYS AS (a * 2) STORED +); +CREATE FUNCTION gtest_trigger_func() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + IF tg_op IN ('DELETE', 'UPDATE') THEN + RAISE INFO '%: %: old = %', TG_NAME, TG_WHEN, OLD; + END IF; + IF tg_op IN ('INSERT', 'UPDATE') THEN + RAISE INFO '%: %: new = %', TG_NAME, TG_WHEN, NEW; + END IF; + IF tg_op = 'DELETE' THEN + RETURN OLD; + ELSE + RETURN NEW; + END IF; +END +$$; +CREATE TRIGGER gtest1 BEFORE DELETE OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (OLD.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); +CREATE TRIGGER gtest2a BEFORE INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.b < 0) -- error + EXECUTE PROCEDURE gtest_trigger_func(); +ERROR: BEFORE trigger's WHEN condition cannot reference NEW generated columns +LINE 3: WHEN (NEW.b < 0) + ^ +DETAIL: Column "b" is a generated column. +CREATE TRIGGER gtest2b BEFORE INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.* IS NOT NULL) -- error + EXECUTE PROCEDURE gtest_trigger_func(); +ERROR: BEFORE trigger's WHEN condition cannot reference NEW generated columns +LINE 3: WHEN (NEW.* IS NOT NULL) + ^ +DETAIL: A whole-row reference is used and the table contains generated columns. +CREATE TRIGGER gtest2 BEFORE INSERT ON gtest26 + FOR EACH ROW + WHEN (NEW.a < 0) + EXECUTE PROCEDURE gtest_trigger_func(); +CREATE TRIGGER gtest3 AFTER DELETE OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (OLD.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); +CREATE TRIGGER gtest4 AFTER INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); +INSERT INTO gtest26 (a) VALUES (-2), (0), (3); +INFO: gtest2: BEFORE: new = (-2,) +INFO: gtest4: AFTER: new = (-2,-4) +SELECT * FROM gtest26 ORDER BY a; + a | b +----+---- + -2 | -4 + 0 | 0 + 3 | 6 +(3 rows) + +UPDATE gtest26 SET a = a * -2; +INFO: gtest1: BEFORE: old = (-2,-4) +INFO: gtest1: BEFORE: new = (4,) +INFO: gtest3: AFTER: old = (-2,-4) +INFO: gtest3: AFTER: new = (4,8) +INFO: gtest4: AFTER: old = (3,6) +INFO: gtest4: AFTER: new = (-6,-12) +SELECT * FROM gtest26 ORDER BY a; + a | b +----+----- + -6 | -12 + 0 | 0 + 4 | 8 +(3 rows) + +DELETE FROM gtest26 WHERE a = -6; +INFO: gtest1: BEFORE: old = (-6,-12) +INFO: gtest3: AFTER: old = (-6,-12) +SELECT * FROM gtest26 ORDER BY a; + a | b +---+--- + 0 | 0 + 4 | 8 +(2 rows) + +DROP TRIGGER gtest1 ON gtest26; +DROP TRIGGER gtest2 ON gtest26; +DROP TRIGGER gtest3 ON gtest26; +-- Check that an UPDATE of "a" fires the trigger for UPDATE OF b, per +-- SQL standard. +CREATE FUNCTION gtest_trigger_func3() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + RAISE NOTICE 'OK'; + RETURN NEW; +END +$$; +CREATE TRIGGER gtest11 BEFORE UPDATE OF b ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func3(); +UPDATE gtest26 SET a = 1 WHERE a = 0; +NOTICE: OK +DROP TRIGGER gtest11 ON gtest26; +TRUNCATE gtest26; +-- check that modifications of stored generated columns in triggers do +-- not get propagated +CREATE FUNCTION gtest_trigger_func4() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + NEW.a = 10; + NEW.b = 300; + RETURN NEW; +END; +$$; +CREATE TRIGGER gtest12_01 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func(); +CREATE TRIGGER gtest12_02 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func4(); +CREATE TRIGGER gtest12_03 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func(); +INSERT INTO gtest26 (a) VALUES (1); +UPDATE gtest26 SET a = 11 WHERE a = 1; +INFO: gtest12_01: BEFORE: old = (1,2) +INFO: gtest12_01: BEFORE: new = (11,) +INFO: gtest12_03: BEFORE: old = (1,2) +INFO: gtest12_03: BEFORE: new = (10,) +SELECT * FROM gtest26 ORDER BY a; + a | b +----+---- + 10 | 20 +(1 row) + +-- LIKE INCLUDING GENERATED and dropped column handling +CREATE TABLE gtest28a ( + a int, + b int, + c int, + x int GENERATED ALWAYS AS (b * 2) STORED +); +ALTER TABLE gtest28a DROP COLUMN a; +CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED); +\d gtest28* + Table "public.gtest28a" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + b | integer | | | + c | integer | | | + x | integer | | | generated always as (b * 2) stored + + Table "public.gtest28b" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + b | integer | | | + c | integer | | | + x | integer | | | generated always as (b * 2) stored + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index de4989ff94..582a7253e4 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi # ---------- # Another group of parallel tests # ---------- -test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity +test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 175ee263b6..7c240d9e4e 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -122,6 +122,7 @@ test: groupingsets test: drop_operator test: password test: identity +test: generated test: create_table_like test: alter_generic test: alter_operator diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql index 65c3880792..9b19c680b5 100644 --- a/src/test/regress/sql/create_table_like.sql +++ b/src/test/regress/sql/create_table_like.sql @@ -51,6 +51,20 @@ CREATE TABLE test_like_id_3 (LIKE test_like_id_1 INCLUDING IDENTITY); SELECT * FROM test_like_id_3; -- identity was copied and applied DROP TABLE test_like_id_1, test_like_id_2, test_like_id_3; +CREATE TABLE test_like_gen_1 (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +\d test_like_gen_1 +INSERT INTO test_like_gen_1 (a) VALUES (1); +SELECT * FROM test_like_gen_1; +CREATE TABLE test_like_gen_2 (LIKE test_like_gen_1); +\d test_like_gen_2 +INSERT INTO test_like_gen_2 (a) VALUES (1); +SELECT * FROM test_like_gen_2; +CREATE TABLE test_like_gen_3 (LIKE test_like_gen_1 INCLUDING GENERATED); +\d test_like_gen_3 +INSERT INTO test_like_gen_3 (a) VALUES (1); +SELECT * FROM test_like_gen_3; +DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3; + CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */ INSERT INTO inhg VALUES (5, 10); INSERT INTO inhg VALUES (20, 10); -- should fail diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql new file mode 100644 index 0000000000..f99d95a037 --- /dev/null +++ b/src/test/regress/sql/generated.sql @@ -0,0 +1,436 @@ +-- sanity check of system catalog +SELECT attrelid, attname, attgenerated FROM pg_attribute WHERE attgenerated NOT IN ('', 's'); + + +CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55) STORED); +CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); + +SELECT table_name, column_name, column_default, is_nullable, is_generated, generation_expression FROM information_schema.columns WHERE table_name LIKE 'gtest_' ORDER BY 1, 2; + +SELECT table_name, column_name, dependent_column FROM information_schema.column_column_usage ORDER BY 1, 2, 3; + +\d gtest1 + +-- duplicate generated +CREATE TABLE gtest_err_1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ALWAYS AS (a * 3) STORED); + +-- references to other generated columns, including self-references +CREATE TABLE gtest_err_2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STORED); +CREATE TABLE gtest_err_2b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STORED); + +-- invalid reference +CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STORED); + +-- generation expression must be immutable +CREATE TABLE gtest_err_4 (a int PRIMARY KEY, b double precision GENERATED ALWAYS AS (random()) STORED); + +-- cannot have default/identity and generated +CREATE TABLE gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ALWAYS AS (a * 2) STORED); +CREATE TABLE gtest_err_5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ALWAYS AS (a * 2) STORED); + +-- reference to system column not allowed in generated column +CREATE TABLE gtest_err_6a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37) STORED); + +INSERT INTO gtest1 VALUES (1); +INSERT INTO gtest1 VALUES (2, DEFAULT); +INSERT INTO gtest1 VALUES (3, 33); -- error + +SELECT * FROM gtest1 ORDER BY a; + +UPDATE gtest1 SET b = DEFAULT WHERE a = 1; +UPDATE gtest1 SET b = 11 WHERE a = 1; -- error + +SELECT * FROM gtest1 ORDER BY a; + +SELECT a, b, b * 2 AS b2 FROM gtest1 ORDER BY a; +SELECT a, b FROM gtest1 WHERE b = 4 ORDER BY a; + +-- test that overflow error happens on write +INSERT INTO gtest1 VALUES (2000000000); +SELECT * FROM gtest1; +DELETE FROM gtest1 WHERE a = 2000000000; + +-- test with joins +CREATE TABLE gtestx (x int, y int); +INSERT INTO gtestx VALUES (11, 1), (22, 2), (33, 3); +SELECT * FROM gtestx, gtest1 WHERE gtestx.y = gtest1.a; +DROP TABLE gtestx; + +-- test UPDATE/DELETE quals +SELECT * FROM gtest1 ORDER BY a; +UPDATE gtest1 SET a = 3 WHERE b = 4; +SELECT * FROM gtest1 ORDER BY a; +DELETE FROM gtest1 WHERE b = 2; +SELECT * FROM gtest1 ORDER BY a; + +-- views +CREATE VIEW gtest1v AS SELECT * FROM gtest1; +SELECT * FROM gtest1v; +INSERT INTO gtest1v VALUES (4, 8); -- fails +DROP VIEW gtest1v; + +-- CTEs +WITH foo AS (SELECT * FROM gtest1) SELECT * FROM foo; + +-- inheritance +CREATE TABLE gtest1_1 () INHERITS (gtest1); +SELECT * FROM gtest1_1; +\d gtest1_1 +INSERT INTO gtest1_1 VALUES (4); +SELECT * FROM gtest1_1; +SELECT * FROM gtest1; + +-- test inheritance mismatch +CREATE TABLE gtesty (x int, b int); +CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty); -- error +DROP TABLE gtesty; + +-- test stored update +CREATE TABLE gtest3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 3) STORED); +INSERT INTO gtest3 (a) VALUES (1), (2), (3); +SELECT * FROM gtest3 ORDER BY a; +UPDATE gtest3 SET a = 22 WHERE a = 2; +SELECT * FROM gtest3 ORDER BY a; + +-- COPY +TRUNCATE gtest1; +INSERT INTO gtest1 (a) VALUES (1), (2); + +COPY gtest1 TO stdout; + +COPY gtest1 (a, b) TO stdout; + +COPY gtest1 FROM stdin; +3 +4 +\. + +COPY gtest1 (a, b) FROM stdin; + +SELECT * FROM gtest1 ORDER BY a; + +TRUNCATE gtest3; +INSERT INTO gtest3 (a) VALUES (1), (2); + +COPY gtest3 TO stdout; + +COPY gtest3 (a, b) TO stdout; + +COPY gtest3 FROM stdin; +3 +4 +\. + +COPY gtest3 (a, b) FROM stdin; + +SELECT * FROM gtest3 ORDER BY a; + +-- null values +CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) STORED); +INSERT INTO gtest2 VALUES (1); +SELECT * FROM gtest2; + +-- composite types +CREATE TYPE double_int as (a int, b int); +CREATE TABLE gtest4 ( + a int, + b double_int GENERATED ALWAYS AS ((a * 2, a * 3)) STORED +); +INSERT INTO gtest4 VALUES (1), (6); +SELECT * FROM gtest4; + +DROP TABLE gtest4; +DROP TYPE double_int; + +-- using tableoid is allowed +CREATE TABLE gtest_tableoid ( + a int PRIMARY KEY, + b bool GENERATED ALWAYS AS (tableoid <> 0) STORED +); +INSERT INTO gtest_tableoid VALUES (1), (2); +SELECT * FROM gtest_tableoid; + +-- drop column behavior +CREATE TABLE gtest10 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED); +ALTER TABLE gtest10 DROP COLUMN b; + +\d gtest10 + +-- privileges +CREATE USER regress_user11; + +CREATE TABLE gtest11s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED); +INSERT INTO gtest11s VALUES (1, 10), (2, 20); +GRANT SELECT (a, c) ON gtest11s TO regress_user11; + +CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL; +REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC; + +CREATE TABLE gtest12s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) STORED); +INSERT INTO gtest12s VALUES (1, 10), (2, 20); +GRANT SELECT (a, c) ON gtest12s TO regress_user11; + +SET ROLE regress_user11; +SELECT a, b FROM gtest11s; -- not allowed +SELECT a, c FROM gtest11s; -- allowed +SELECT gf1(10); -- not allowed +SELECT a, c FROM gtest12s; -- allowed +RESET ROLE; + +DROP TABLE gtest11s, gtest12s; +DROP FUNCTION gf1(int); +DROP USER regress_user11; + +-- check constraints +CREATE TABLE gtest20 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED CHECK (b < 50)); +INSERT INTO gtest20 (a) VALUES (10); -- ok +INSERT INTO gtest20 (a) VALUES (30); -- violates constraint + +CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest20a (a) VALUES (10); +INSERT INTO gtest20a (a) VALUES (30); +ALTER TABLE gtest20a ADD CHECK (b < 50); -- fails on existing row + +CREATE TABLE gtest20b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest20b (a) VALUES (10); +INSERT INTO gtest20b (a) VALUES (30); +ALTER TABLE gtest20b ADD CONSTRAINT chk CHECK (b < 50) NOT VALID; +ALTER TABLE gtest20b VALIDATE CONSTRAINT chk; -- fails on existing row + +-- not-null constraints +CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED NOT NULL); +INSERT INTO gtest21a (a) VALUES (1); -- ok +INSERT INTO gtest21a (a) VALUES (0); -- violates constraint + +CREATE TABLE gtest21b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED); +ALTER TABLE gtest21b ALTER COLUMN b SET NOT NULL; +INSERT INTO gtest21b (a) VALUES (1); -- ok +INSERT INTO gtest21b (a) VALUES (0); -- violates constraint +ALTER TABLE gtest21b ALTER COLUMN b DROP NOT NULL; +INSERT INTO gtest21b (a) VALUES (0); -- ok now + +-- index constraints +CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED UNIQUE); +CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a * 2) STORED, PRIMARY KEY (a, b)); + +-- indexes +CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +CREATE INDEX gtest22c_b_idx ON gtest22c (b); +CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); +CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; +\d gtest22c + +INSERT INTO gtest22c VALUES (1), (2), (3); +SET enable_seqscan TO off; +SET enable_bitmapscan TO off; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4; +SELECT * FROM gtest22c WHERE b = 4; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6; +SELECT * FROM gtest22c WHERE b * 3 = 6; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; +SELECT * FROM gtest22c WHERE a = 1 AND b > 0; +RESET enable_seqscan; +RESET enable_bitmapscan; + +-- foreign keys +CREATE TABLE gtest23a (x int PRIMARY KEY, y int); +INSERT INTO gtest23a VALUES (1, 11), (2, 22), (3, 33); + +CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON UPDATE CASCADE); -- error +CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON DELETE SET NULL); -- error + +CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x)); +\d gtest23b + +INSERT INTO gtest23b VALUES (1); -- ok +INSERT INTO gtest23b VALUES (5); -- error + +DROP TABLE gtest23b; +DROP TABLE gtest23a; + +CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) STORED, PRIMARY KEY (y)); +INSERT INTO gtest23p VALUES (1), (2), (3); + +CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y)); +INSERT INTO gtest23q VALUES (1, 2); -- ok +INSERT INTO gtest23q VALUES (2, 5); -- error + +-- domains +CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10); +CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest24 (a) VALUES (4); -- ok +INSERT INTO gtest24 (a) VALUES (6); -- error + +-- typed tables (currently not supported) +CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint); +CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) STORED); +DROP TYPE gtest_type CASCADE; + +-- table partitions (currently not supported) +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); +CREATE TABLE gtest_child PARTITION OF gtest_parent ( + f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 2) STORED +) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error +DROP TABLE gtest_parent; + +-- partitioned table +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f1); +CREATE TABLE gtest_child PARTITION OF gtest_parent FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1); +SELECT * FROM gtest_parent; +SELECT * FROM gtest_child; +DROP TABLE gtest_parent; + +-- generated columns in partition key (not allowed) +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); + +-- ALTER TABLE ... ADD COLUMN +CREATE TABLE gtest25 (a int PRIMARY KEY); +INSERT INTO gtest25 VALUES (3), (4); +ALTER TABLE gtest25 ADD COLUMN b int GENERATED ALWAYS AS (a * 3) STORED; +SELECT * FROM gtest25 ORDER BY a; +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4) STORED; -- error +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error + +-- ALTER TABLE ... ALTER COLUMN +CREATE TABLE gtest27 ( + a int, + b int GENERATED ALWAYS AS (a * 2) STORED +); +INSERT INTO gtest27 (a) VALUES (3), (4); +ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error +ALTER TABLE gtest27 ALTER COLUMN b TYPE numeric; +\d gtest27 +SELECT * FROM gtest27; +ALTER TABLE gtest27 ALTER COLUMN b TYPE boolean USING b <> 0; -- error + +ALTER TABLE gtest27 ALTER COLUMN b DROP DEFAULT; -- error +\d gtest27 + +-- triggers +CREATE TABLE gtest26 ( + a int PRIMARY KEY, + b int GENERATED ALWAYS AS (a * 2) STORED +); + +CREATE FUNCTION gtest_trigger_func() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + IF tg_op IN ('DELETE', 'UPDATE') THEN + RAISE INFO '%: %: old = %', TG_NAME, TG_WHEN, OLD; + END IF; + IF tg_op IN ('INSERT', 'UPDATE') THEN + RAISE INFO '%: %: new = %', TG_NAME, TG_WHEN, NEW; + END IF; + IF tg_op = 'DELETE' THEN + RETURN OLD; + ELSE + RETURN NEW; + END IF; +END +$$; + +CREATE TRIGGER gtest1 BEFORE DELETE OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (OLD.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest2a BEFORE INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.b < 0) -- error + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest2b BEFORE INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.* IS NOT NULL) -- error + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest2 BEFORE INSERT ON gtest26 + FOR EACH ROW + WHEN (NEW.a < 0) + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest3 AFTER DELETE OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (OLD.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest4 AFTER INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); + +INSERT INTO gtest26 (a) VALUES (-2), (0), (3); +SELECT * FROM gtest26 ORDER BY a; +UPDATE gtest26 SET a = a * -2; +SELECT * FROM gtest26 ORDER BY a; +DELETE FROM gtest26 WHERE a = -6; +SELECT * FROM gtest26 ORDER BY a; + +DROP TRIGGER gtest1 ON gtest26; +DROP TRIGGER gtest2 ON gtest26; +DROP TRIGGER gtest3 ON gtest26; + +-- Check that an UPDATE of "a" fires the trigger for UPDATE OF b, per +-- SQL standard. +CREATE FUNCTION gtest_trigger_func3() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + RAISE NOTICE 'OK'; + RETURN NEW; +END +$$; + +CREATE TRIGGER gtest11 BEFORE UPDATE OF b ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func3(); + +UPDATE gtest26 SET a = 1 WHERE a = 0; + +DROP TRIGGER gtest11 ON gtest26; +TRUNCATE gtest26; + +-- check that modifications of stored generated columns in triggers do +-- not get propagated +CREATE FUNCTION gtest_trigger_func4() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + NEW.a = 10; + NEW.b = 300; + RETURN NEW; +END; +$$; + +CREATE TRIGGER gtest12_01 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest12_02 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func4(); + +CREATE TRIGGER gtest12_03 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func(); + +INSERT INTO gtest26 (a) VALUES (1); +UPDATE gtest26 SET a = 11 WHERE a = 1; +SELECT * FROM gtest26 ORDER BY a; + +-- LIKE INCLUDING GENERATED and dropped column handling +CREATE TABLE gtest28a ( + a int, + b int, + c int, + x int GENERATED ALWAYS AS (b * 2) STORED +); + +ALTER TABLE gtest28a DROP COLUMN a; + +CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED); + +\d gtest28* diff --git a/src/test/subscription/t/011_generated.pl b/src/test/subscription/t/011_generated.pl new file mode 100644 index 0000000000..f7456e9216 --- /dev/null +++ b/src/test/subscription/t/011_generated.pl @@ -0,0 +1,65 @@ +# Test generated columns +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 2; + +# setup + +my $node_publisher = get_new_node('publisher'); +$node_publisher->init(allows_streaming => 'logical'); +$node_publisher->start; + +my $node_subscriber = get_new_node('subscriber'); +$node_subscriber->init(allows_streaming => 'logical'); +$node_subscriber->start; + +my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres'; + +$node_publisher->safe_psql('postgres', + "CREATE TABLE tab1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED)"); + +$node_subscriber->safe_psql('postgres', + "CREATE TABLE tab1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 22) STORED)"); + +# data for initial sync + +$node_publisher->safe_psql('postgres', + "INSERT INTO tab1 (a) VALUES (1), (2), (3)"); + +$node_publisher->safe_psql('postgres', + "CREATE PUBLICATION pub1 FOR ALL TABLES"); +$node_subscriber->safe_psql('postgres', + "CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr' PUBLICATION pub1" +); + +# Wait for initial sync of all subscriptions +my $synced_query = + "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');"; +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +my $result = $node_subscriber->safe_psql('postgres', + "SELECT a, b FROM tab1"); +is($result, qq(1|22 +2|44 +3|66), 'generated columns initial sync'); + +# data to replicate + +$node_publisher->safe_psql('postgres', + "INSERT INTO tab1 VALUES (4), (5)"); + +$node_publisher->safe_psql('postgres', + "UPDATE tab1 SET a = 6 WHERE a = 5"); + +$node_publisher->wait_for_catchup('sub1'); + +$result = $node_subscriber->safe_psql('postgres', + "SELECT a, b FROM tab1"); +is($result, qq(1|22 +2|44 +3|66 +4|88 +6|132), 'generated columns replicated'); base-commit: 4178d8b91cb943b422d1837b4b7798576d88995a -- 2.21.0 --------------567D0C528C354BD1F1241472-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v10] Generated columns @ 2019-03-26 13:31 Peter Eisentraut <peter@eisentraut.org> 0 siblings, 0 replies; 114+ messages in thread From: Peter Eisentraut @ 2019-03-26 13:31 UTC (permalink / raw) This is an SQL-standard feature that allows creating columns that are computed from expressions rather than assigned, similar to a view or materialized view but on a column basis. This implement one kind of generated column: stored (computed on write). Another kind, virtual (computed on read), is planned for the future, and some room is left for it. Discussion: https://www.postgresql.org/message-id/flat/b151f851-4019-bdb1-699e-ebab07d2f40a@2ndquadrant.com --- .../postgres_fdw/expected/postgres_fdw.out | 25 + contrib/postgres_fdw/postgres_fdw.c | 3 +- contrib/postgres_fdw/sql/postgres_fdw.sql | 14 + doc/src/sgml/catalogs.sgml | 19 +- doc/src/sgml/ddl.sgml | 118 +++ doc/src/sgml/information_schema.sgml | 66 +- doc/src/sgml/protocol.sgml | 4 +- doc/src/sgml/ref/copy.sgml | 3 +- doc/src/sgml/ref/create_foreign_table.sgml | 27 +- doc/src/sgml/ref/create_table.sgml | 45 +- doc/src/sgml/ref/create_trigger.sgml | 4 +- doc/src/sgml/textsearch.sgml | 26 +- doc/src/sgml/trigger.sgml | 18 + src/backend/access/common/tupdesc.c | 11 + src/backend/catalog/heap.c | 95 ++- src/backend/catalog/information_schema.sql | 30 +- src/backend/commands/copy.c | 31 +- src/backend/commands/tablecmds.c | 167 +++- src/backend/commands/trigger.c | 31 +- src/backend/commands/typecmds.c | 6 +- src/backend/executor/execMain.c | 8 +- src/backend/executor/execReplication.c | 11 + src/backend/executor/nodeModifyTable.c | 112 +++ src/backend/nodes/copyfuncs.c | 2 + src/backend/nodes/equalfuncs.c | 2 + src/backend/nodes/outfuncs.c | 9 + src/backend/nodes/readfuncs.c | 1 + src/backend/optimizer/plan/createplan.c | 8 +- src/backend/optimizer/util/inherit.c | 6 + src/backend/optimizer/util/plancat.c | 19 + src/backend/parser/analyze.c | 27 + src/backend/parser/gram.y | 14 +- src/backend/parser/parse_agg.c | 11 + src/backend/parser/parse_expr.c | 5 + src/backend/parser/parse_func.c | 3 + src/backend/parser/parse_relation.c | 19 + src/backend/parser/parse_utilcmd.c | 66 +- src/backend/replication/logical/proto.c | 9 +- src/backend/replication/logical/relation.c | 2 +- src/backend/replication/logical/tablesync.c | 6 +- src/backend/replication/logical/worker.c | 2 +- src/backend/replication/pgoutput/pgoutput.c | 2 +- src/backend/rewrite/rewriteHandler.c | 36 +- src/backend/utils/cache/lsyscache.c | 33 + src/backend/utils/cache/partcache.c | 1 + src/backend/utils/cache/relcache.c | 4 + src/bin/pg_dump/pg_dump.c | 40 +- src/bin/pg_dump/pg_dump.h | 1 + src/bin/pg_dump/pg_dump_sort.c | 10 + src/bin/pg_dump/t/002_pg_dump.pl | 17 + src/bin/psql/describe.c | 23 +- src/include/access/tupdesc.h | 1 + src/include/catalog/heap.h | 4 +- src/include/catalog/pg_attribute.h | 5 + src/include/catalog/pg_class.dat | 2 +- src/include/executor/nodeModifyTable.h | 2 + src/include/nodes/execnodes.h | 3 + src/include/nodes/parsenodes.h | 24 +- src/include/optimizer/plancat.h | 2 + src/include/parser/kwlist.h | 1 + src/include/parser/parse_node.h | 3 +- src/include/utils/lsyscache.h | 1 + src/pl/plperl/expected/plperl_trigger.out | 95 +++ src/pl/plperl/plperl.c | 40 +- src/pl/plperl/sql/plperl_trigger.sql | 36 + src/pl/plpgsql/src/pl_exec.c | 20 + src/pl/plpython/expected/plpython_trigger.out | 94 +++ src/pl/plpython/plpy_cursorobject.c | 5 +- src/pl/plpython/plpy_exec.c | 23 +- src/pl/plpython/plpy_spi.c | 3 +- src/pl/plpython/plpy_typeio.c | 17 +- src/pl/plpython/plpy_typeio.h | 2 +- src/pl/plpython/sql/plpython_trigger.sql | 37 + src/pl/tcl/expected/pltcl_trigger.out | 99 +++ src/pl/tcl/pltcl.c | 50 +- src/pl/tcl/sql/pltcl_trigger.sql | 36 + .../regress/expected/create_table_like.out | 46 ++ src/test/regress/expected/generated.out | 768 ++++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 1 + src/test/regress/sql/create_table_like.sql | 14 + src/test/regress/sql/generated.sql | 451 ++++++++++ src/test/subscription/t/011_generated.pl | 65 ++ 83 files changed, 3055 insertions(+), 149 deletions(-) create mode 100644 src/test/regress/expected/generated.out create mode 100644 src/test/regress/sql/generated.sql create mode 100644 src/test/subscription/t/011_generated.pl diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 2be67bca02..8b3768028f 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -6431,6 +6431,31 @@ select * from rem1; 11 | bye remote (4 rows) +-- =================================================================== +-- test generated columns +-- =================================================================== +create table gloc1 (a int, b int); +alter table gloc1 set (autovacuum_enabled = 'false'); +create foreign table grem1 ( + a int, + b int generated always as (a * 2) stored) + server loopback options(table_name 'gloc1'); +insert into grem1 (a) values (1), (2); +update grem1 set a = 22 where a = 2; +select * from gloc1; + a | b +----+---- + 1 | 2 + 22 | 44 +(2 rows) + +select * from grem1; + a | b +----+---- + 1 | 2 + 22 | 44 +(2 rows) + -- =================================================================== -- test local triggers -- =================================================================== diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 2f387fac42..d0d36aaa0d 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -1644,9 +1644,10 @@ postgresPlanForeignModify(PlannerInfo *root, else if (operation == CMD_UPDATE) { int col; + Bitmapset *allUpdatedCols = bms_union(rte->updatedCols, rte->extraUpdatedCols); col = -1; - while ((col = bms_next_member(rte->updatedCols, col)) >= 0) + while ((col = bms_next_member(allUpdatedCols, col)) >= 0) { /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */ AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber; diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 4728511abf..a96c327134 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -1363,6 +1363,20 @@ CREATE VIEW rw_view AS SELECT * FROM parent_tbl select * from loc1; select * from rem1; +-- =================================================================== +-- test generated columns +-- =================================================================== +create table gloc1 (a int, b int); +alter table gloc1 set (autovacuum_enabled = 'false'); +create foreign table grem1 ( + a int, + b int generated always as (a * 2) stored) + server loopback options(table_name 'gloc1'); +insert into grem1 (a) values (1), (2); +update grem1 set a = 22 where a = 2; +select * from gloc1; +select * from grem1; + -- =================================================================== -- test local triggers -- =================================================================== diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 45ed077654..bf3ab132ec 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1129,9 +1129,11 @@ <title><structname>pg_attribute</structname> Columns</title> <entry><type>bool</type></entry> <entry></entry> <entry> - This column has a default value, in which case there will be a - corresponding entry in the <structname>pg_attrdef</structname> - catalog that actually defines the value. + This column has a default expression or generation expression, in which + case there will be a corresponding entry in the + <structname>pg_attrdef</structname> catalog that actually defines the + expression. (Check <structfield>attgenerated</structfield> to + determine whether this is a default or a generation expression.) </entry> </row> @@ -1159,6 +1161,17 @@ <title><structname>pg_attribute</structname> Columns</title> </entry> </row> + <row> + <entry><structfield>attgenerated</structfield></entry> + <entry><type>char</type></entry> + <entry></entry> + <entry> + If a zero byte (<literal>''</literal>), then not a generated column. + Otherwise, <literal>s</literal> = stored. (Other values might be added + in the future.) + </entry> + </row> + <row> <entry><structfield>attisdropped</structfield></entry> <entry><type>bool</type></entry> diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 110f6b4657..1fe27c5da9 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -233,6 +233,124 @@ <title>Default Values</title> </para> </sect1> + <sect1 id="ddl-generated-columns"> + <title>Generated Columns</title> + + <indexterm zone="ddl-generated-columns"> + <primary>generated column</primary> + </indexterm> + + <para> + A generated column is a special column that is always computed from other + columns. Thus, it is for columns what a view is for tables. There are two + kinds of generated columns: stored and virtual. A stored generated column + is computed when it is written (inserted or updated) and occupies storage + as if it were a normal column. A virtual generated column occupies no + storage and is computed when it is read. Thus, a virtual generated column + is similar to a view and a stored generated column is similar to a + materialized view (except that it is always updated automatically). + PostgreSQL currently implements only stored generated columns. + </para> + + <para> + To create a generated column, use the <literal>GENERATED ALWAYS + AS</literal> clause in <command>CREATE TABLE</command>, for example: +<programlisting> +CREATE TABLE people ( + ..., + height_cm numeric, + height_in numeric <emphasis>GENERATED ALWAYS AS (height_cm * 2.54) STORED</emphasis> +); +</programlisting> + The keyword <literal>STORED</literal> must be specified to choose the + stored kind of generated column. See <xref linkend="sql-createtable"/> for + more details. + </para> + + <para> + A generated column cannot be written to directly. In + <command>INSERT</command> or <command>UPDATE</command> commands, a value + cannot be specified for a generated column, but the keyword + <literal>DEFAULT</literal> may be specified. + </para> + + <para> + Consider the differences between a column with a default and a generated + column. The column default is evaluated once when the row is first + inserted if no other value was provided; a generated column is updated + whenever the row changes and cannot be overridden. A column default may + not refer to other columns of the table; a generation expression would + normally do so. A column default can use volatile functions, for example + <literal>random()</literal> or functions referring to the current time; + this is not allowed for generated columns. + </para> + + <para> + Several restrictions apply to the definition of generated columns and + tables involving generated columns: + + <itemizedlist> + <listitem> + <para> + The generation expression can only use immutable functions and cannot + use subqueries or reference anything other than the current row in any + way. + </para> + </listitem> + <listitem> + <para> + A generation expression cannot reference another generated column. + </para> + </listitem> + <listitem> + <para> + A generation expression cannot reference a system column, except + <varname>tableoid</varname>. + </para> + </listitem> + <listitem> + <para> + A generated column cannot have a column default or an identity definition. + </para> + </listitem> + <listitem> + <para> + A generated column cannot be part of a partition key. + </para> + </listitem> + <listitem> + <para> + Foreign tables can have generated columns. See <xref + linkend="sql-createforeigntable"/> for details. + </para> + </listitem> + </itemizedlist> + </para> + + <para> + Additional considerations apply to the use of generated columns. + <itemizedlist> + <listitem> + <para> + Generated columns maintain access privileges separately from their + underlying base columns. So, it is possible to arrange it so that a + particular role can read from a generated column but not from the + underlying base columns. + </para> + </listitem> + <listitem> + <para> + Generated columns are, conceptually, updated after + <literal>BEFORE</literal> triggers have run. Therefore, changes made to + base columns in a <literal>BEFORE</literal> trigger will be reflected in + generated columns. But conversely, it is not allowed to access + generated columns in <literal>BEFORE</literal> triggers. + </para> + </listitem> + </itemizedlist> + </para> + </sect1> + <sect1 id="ddl-constraints"> <title>Constraints</title> diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml index b13700da92..1321ade44a 100644 --- a/doc/src/sgml/information_schema.sgml +++ b/doc/src/sgml/information_schema.sgml @@ -952,6 +952,62 @@ <title><literal>collation_character_set_applicability</literal> Columns</title> </table> </sect1> + <sect1 id="infoschema-column-column-usage"> + <title><literal>column_column_usage</literal></title> + + <para> + The view <literal>column_column_usage</literal> identifies all generated + columns that depend on another base column in the same table. Only tables + owned by a currently enabled role are included. + </para> + + <table> + <title><literal>column_column_usage</literal> Columns</title> + + <tgroup cols="3"> + <thead> + <row> + <entry>Name</entry> + <entry>Data Type</entry> + <entry>Description</entry> + </row> + </thead> + + <tbody> + <row> + <entry><literal>table_catalog</literal></entry> + <entry><type>sql_identifier</type></entry> + <entry>Name of the database containing the table (always the current database)</entry> + </row> + + <row> + <entry><literal>table_schema</literal></entry> + <entry><type>sql_identifier</type></entry> + <entry>Name of the schema containing the table</entry> + </row> + + <row> + <entry><literal>table_name</literal></entry> + <entry><type>sql_identifier</type></entry> + <entry>Name of the table</entry> + </row> + + <row> + <entry><literal>column_name</literal></entry> + <entry><type>sql_identifier</type></entry> + <entry>Name of the base column that a generated column depends on</entry> + </row> + + <row> + <entry><literal>dependent_column</literal></entry> + <entry><type>sql_identifier</type></entry> + <entry>Name of the generated column</entry> + </row> + </tbody> + </tgroup> + </table> + </sect1> + <sect1 id="infoschema-column-domain-usage"> <title><literal>column_domain_usage</literal></title> @@ -1648,13 +1704,19 @@ <title><literal>columns</literal> Columns</title> <row> <entry><literal>is_generated</literal></entry> <entry><type>character_data</type></entry> - <entry>Applies to a feature not available in <productname>PostgreSQL</productname></entry> + <entry> + If the column is a generated column, then <literal>ALWAYS</literal>, + else <literal>NEVER</literal>. + </entry> </row> <row> <entry><literal>generation_expression</literal></entry> <entry><type>character_data</type></entry> - <entry>Applies to a feature not available in <productname>PostgreSQL</productname></entry> + <entry> + If the column is a generated column, then the generation expression, + else null. + </entry> </row> <row> diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index d66b860cbd..a0e1f78bfc 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -6450,7 +6450,7 @@ <title>Logical Replication Message Formats</title> </listitem> </varlistentry> </variablelist> - Next, the following message part appears for each column: + Next, the following message part appears for each column (except generated columns): <variablelist> <varlistentry> <term> @@ -6875,7 +6875,7 @@ <title>Logical Replication Message Formats</title> </listitem> </varlistentry> </variablelist> - Next, one of the following submessages appears for each column: + Next, one of the following submessages appears for each column (except generated columns): <variablelist> <varlistentry> <term> diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml index 254d3ab8eb..5e2992ddac 100644 --- a/doc/src/sgml/ref/copy.sgml +++ b/doc/src/sgml/ref/copy.sgml @@ -103,7 +103,8 @@ <title>Parameters</title> <listitem> <para> An optional list of columns to be copied. If no column list is - specified, all columns of the table will be copied. + specified, all columns of the table except generated columns will be + copied. </para> </listitem> </varlistentry> diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml index 19eb5341e7..65ba3e3d37 100644 --- a/doc/src/sgml/ref/create_foreign_table.sgml +++ b/doc/src/sgml/ref/create_foreign_table.sgml @@ -42,7 +42,8 @@ { NOT NULL | NULL | CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] | - DEFAULT <replaceable>default_expr</replaceable> } + DEFAULT <replaceable>default_expr</replaceable> | + GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED } <phrase>and <replaceable class="parameter">table_constraint</replaceable> is:</phrase> @@ -258,6 +259,30 @@ <title>Parameters</title> </listitem> </varlistentry> + <varlistentry> + <term><literal>GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED</literal><indexterm><primary>generated column</primary></indexterm></term> + <listitem> + <para> + This clause creates the column as a <firstterm>generated + column</firstterm>. The column cannot be written to, and when read it + will be computed from the specified expression. + </para> + + <para> + The keyword <literal>STORED</literal> is required to signify that the + column will be computed on write. (The computed value will be presented + to the foreign-data wrapper for storage and must be returned on + reading.) + </para> + + <para> + The generation expression can refer to other columns in the table, but + not other generated columns. Any functions and operators used must be + immutable. References to other tables are not allowed. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><replaceable class="parameter">server_name</replaceable></term> <listitem> diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index e94fe2c3b6..3c8ee54859 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -62,6 +62,7 @@ NULL | CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] | DEFAULT <replaceable>default_expr</replaceable> | + GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED | GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] | UNIQUE <replaceable class="parameter">index_parameters</replaceable> | PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> | @@ -83,7 +84,7 @@ <phrase>and <replaceable class="parameter">like_option</replaceable> is:</phrase> -{ INCLUDING | EXCLUDING } { COMMENTS | CONSTRAINTS | DEFAULTS | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL } +{ INCLUDING | EXCLUDING } { COMMENTS | CONSTRAINTS | DEFAULTS | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL } <phrase>and <replaceable class="parameter">partition_bound_spec</replaceable> is:</phrase> @@ -627,6 +628,16 @@ <title>Parameters</title> </listitem> </varlistentry> + <varlistentry> + <term><literal>INCLUDING GENERATED</literal></term> + <listitem> + <para> + Any generation expressions of copied column definitions will be + copied. By default, new columns will be regular base columns. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><literal>INCLUDING IDENTITY</literal></term> <listitem> @@ -797,6 +808,28 @@ <title>Parameters</title> </listitem> </varlistentry> + <varlistentry> + <term><literal>GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED</literal><indexterm><primary>generated column</primary></indexterm></term> + <listitem> + <para> + This clause creates the column as a <firstterm>generated + column</firstterm>. The column cannot be written to, and when read it + will be computed from the specified expression. + </para> + + <para> + The keyword <literal>STORED</literal> is required to signify that the + column will be computed on write and will be stored on disk. default. + </para> + + <para> + The generation expression can refer to other columns in the table, but + not other generated columns. Any functions and operators used must be + immutable. References to other tables are not allowed. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><literal>GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ]</literal></term> <listitem> @@ -2028,6 +2061,16 @@ <title>Multiple Identity Columns</title> </para> </refsect2> + <refsect2> + <title>Generated Columns</title> + + <para> + The option <literal>STORED</literal> is not standard but is also used by + other SQL implementations. The SQL standard does not specify the storage + of generated columns. + </para> + </refsect2> + <refsect2> <title><literal>LIKE</literal> Clause</title> diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml index 6514ffc6ae..6456105de6 100644 --- a/doc/src/sgml/ref/create_trigger.sgml +++ b/doc/src/sgml/ref/create_trigger.sgml @@ -261,7 +261,9 @@ <title>Parameters</title> UPDATE OF <replaceable>column_name1</replaceable> [, <replaceable>column_name2</replaceable> ... ] </synopsis> The trigger will only fire if at least one of the listed columns - is mentioned as a target of the <command>UPDATE</command> command. + is mentioned as a target of the <command>UPDATE</command> command + or if one of the listed columns is a generated column that depends on a + column that is the target of the <command>UPDATE</command>. </para> <para> diff --git a/doc/src/sgml/textsearch.sgml b/doc/src/sgml/textsearch.sgml index 3281f7cd33..40888a4d20 100644 --- a/doc/src/sgml/textsearch.sgml +++ b/doc/src/sgml/textsearch.sgml @@ -620,15 +620,17 @@ <title>Creating Indexes</title> <para> Another approach is to create a separate <type>tsvector</type> column - to hold the output of <function>to_tsvector</function>. This example is a + to hold the output of <function>to_tsvector</function>. To keep this + column automatically up to date with its source data, use a stored + generated column. This example is a concatenation of <literal>title</literal> and <literal>body</literal>, using <function>coalesce</function> to ensure that one field will still be indexed when the other is <literal>NULL</literal>: <programlisting> -ALTER TABLE pgweb ADD COLUMN textsearchable_index_col tsvector; -UPDATE pgweb SET textsearchable_index_col = - to_tsvector('english', coalesce(title,'') || ' ' || coalesce(body,'')); +ALTER TABLE pgweb + ADD COLUMN textsearchable_index_col tsvector + GENERATED ALWAYS AS (to_tsvector('english', coalesce(title, '') || ' ' || coalesce(body, ''))) STORED; </programlisting> Then we create a <acronym>GIN</acronym> index to speed up the search: @@ -648,14 +650,6 @@ <title>Creating Indexes</title> </programlisting> </para> - <para> - When using a separate column to store the <type>tsvector</type> - representation, - it is necessary to create a trigger to keep the <type>tsvector</type> - column current anytime <literal>title</literal> or <literal>body</literal> changes. - <xref linkend="textsearch-update-triggers"/> explains how to do that. - </para> - <para> One advantage of the separate-column approach over an expression index is that it is not necessary to explicitly specify the text search @@ -1857,6 +1851,14 @@ <title>Triggers for Automatic Updates</title> <secondary>for updating a derived tsvector column</secondary> </indexterm> + <note> + <para> + The method described in this section has been obsoleted by the use of + stored generated columns, as described in <xref + linkend="textsearch-tables-index"/>. + </para> + </note> + <para> When using a separate column to store the <type>tsvector</type> representation of your documents, it is necessary to create a trigger to update the diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml index be9c228448..67e1861e06 100644 --- a/doc/src/sgml/trigger.sgml +++ b/doc/src/sgml/trigger.sgml @@ -243,6 +243,24 @@ <title>Overview of Trigger Behavior</title> operation, and so they can return <symbol>NULL</symbol>. </para> + <para> + Some considerations apply for generated + columns.<indexterm><primary>generated column</primary><secondary>in + triggers</secondary></indexterm> Stored generated columns are computed after + <literal>BEFORE</literal> triggers and before <literal>AFTER</literal> + triggers. Therefore, the generated value can be inspected in + <literal>AFTER</literal> triggers. In <literal>BEFORE</literal> triggers, + the <literal>OLD</literal> row contains the old generated value, as one + would expect, but the <literal>NEW</literal> row does not yet contain the + new generated value and should not be accessed. In the C language + interface, the content of the column is undefined at this point; a + higher-level programming language should prevent access to a stored + generated column in the <literal>NEW</literal> row in a + <literal>BEFORE</literal> trigger. Changes to the value of a generated + column in a <literal>BEFORE</literal> trigger are ignored and will be + overwritten. + </para> + <para> If more than one trigger is defined for the same event on the same relation, the triggers will be fired in alphabetical order by diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 0158950a43..6bc4e4c036 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -131,6 +131,7 @@ CreateTupleDescCopy(TupleDesc tupdesc) att->atthasdef = false; att->atthasmissing = false; att->attidentity = '\0'; + att->attgenerated = '\0'; } /* We can copy the tuple type identification, too */ @@ -165,6 +166,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc) TupleConstr *cpy = (TupleConstr *) palloc0(sizeof(TupleConstr)); cpy->has_not_null = constr->has_not_null; + cpy->has_generated_stored = constr->has_generated_stored; if ((cpy->num_defval = constr->num_defval) > 0) { @@ -247,6 +249,7 @@ TupleDescCopy(TupleDesc dst, TupleDesc src) att->atthasdef = false; att->atthasmissing = false; att->attidentity = '\0'; + att->attgenerated = '\0'; } dst->constr = NULL; @@ -300,6 +303,7 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno, dstAtt->atthasdef = false; dstAtt->atthasmissing = false; dstAtt->attidentity = '\0'; + dstAtt->attgenerated = '\0'; } /* @@ -456,6 +460,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) return false; if (attr1->attidentity != attr2->attidentity) return false; + if (attr1->attgenerated != attr2->attgenerated) + return false; if (attr1->attisdropped != attr2->attisdropped) return false; if (attr1->attislocal != attr2->attislocal) @@ -476,6 +482,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) return false; if (constr1->has_not_null != constr2->has_not_null) return false; + if (constr1->has_generated_stored != constr2->has_generated_stored) + return false; n = constr1->num_defval; if (n != (int) constr2->num_defval) return false; @@ -638,6 +646,7 @@ TupleDescInitEntry(TupleDesc desc, att->atthasdef = false; att->atthasmissing = false; att->attidentity = '\0'; + att->attgenerated = '\0'; att->attisdropped = false; att->attislocal = true; att->attinhcount = 0; @@ -697,6 +706,7 @@ TupleDescInitBuiltinEntry(TupleDesc desc, att->atthasdef = false; att->atthasmissing = false; att->attidentity = '\0'; + att->attgenerated = '\0'; att->attisdropped = false; att->attislocal = true; att->attinhcount = 0; @@ -853,6 +863,7 @@ BuildDescForRelation(List *schema) TupleConstr *constr = (TupleConstr *) palloc0(sizeof(TupleConstr)); constr->has_not_null = true; + constr->has_generated_stored = false; constr->defval = NULL; constr->missing = NULL; constr->num_defval = 0; diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index c7b5ff62f9..79020e12e7 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -70,6 +70,7 @@ #include "parser/parse_collate.h" #include "parser/parse_expr.h" #include "parser/parse_relation.h" +#include "parser/parsetree.h" #include "partitioning/partdesc.h" #include "storage/lmgr.h" #include "storage/predicate.h" @@ -687,6 +688,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef); values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing); values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity); + values[Anum_pg_attribute_attgenerated - 1] = CharGetDatum(new_attribute->attgenerated); values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped); values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal); values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount); @@ -1653,6 +1655,9 @@ RemoveAttributeById(Oid relid, AttrNumber attnum) /* We don't want to keep stats for it anymore */ attStruct->attstattarget = 0; + /* Unset this so no one tries to look up the generation expression */ + attStruct->attgenerated = '\0'; + /* * Change the column name to something that isn't likely to conflict */ @@ -2152,6 +2157,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, Relation attrrel; HeapTuple atttup; Form_pg_attribute attStruct; + char attgenerated; Oid attrdefOid; ObjectAddress colobject, defobject; @@ -2199,6 +2205,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, elog(ERROR, "cache lookup failed for attribute %d of relation %u", attnum, RelationGetRelid(rel)); attStruct = (Form_pg_attribute) GETSTRUCT(atttup); + attgenerated = attStruct->attgenerated; if (!attStruct->atthasdef) { Form_pg_attribute defAttStruct; @@ -2219,7 +2226,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, valuesAtt[Anum_pg_attribute_atthasdef - 1] = true; replacesAtt[Anum_pg_attribute_atthasdef - 1] = true; - if (add_column_mode) + if (add_column_mode && !attgenerated) { expr2 = expression_planner(expr2); estate = CreateExecutorState(); @@ -2281,7 +2288,26 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, /* * Record dependencies on objects used in the expression, too. */ - recordDependencyOnExpr(&defobject, expr, NIL, DEPENDENCY_NORMAL); + if (attgenerated) + { + /* + * Generated column: Dropping anything that the generation expression + * refers to automatically drops the generated column. + */ + recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel), + DEPENDENCY_AUTO, + DEPENDENCY_AUTO, false); + } + else + { + /* + * Normal default: Dropping anything that the default refers to + * requires CASCADE and drops the default only. + */ + recordDependencyOnSingleRelExpr(&defobject, expr, RelationGetRelid(rel), + DEPENDENCY_NORMAL, + DEPENDENCY_NORMAL, false); + } /* * Post creation hook for attribute defaults. @@ -2539,12 +2565,14 @@ AddRelationNewConstraints(Relation rel, expr = cookDefault(pstate, colDef->raw_default, atp->atttypid, atp->atttypmod, - NameStr(atp->attname)); + NameStr(atp->attname), + atp->attgenerated); /* * If the expression is just a NULL constant, we do not bother to make * an explicit pg_attrdef entry, since the default behavior is - * equivalent. + * equivalent. This applies to column defaults, but not for generation + * expressions. * * Note a nonobvious property of this test: if the column is of a * domain type, what we'll get is not a bare null Const but a @@ -2553,7 +2581,9 @@ AddRelationNewConstraints(Relation rel, * override any default that the domain might have. */ if (expr == NULL || - (IsA(expr, Const) &&((Const *) expr)->constisnull)) + (!colDef->generated && + IsA(expr, Const) && + castNode(Const, expr)->constisnull)) continue; /* If the DEFAULT is volatile we cannot use a missing value */ @@ -2910,6 +2940,46 @@ SetRelationNumChecks(Relation rel, int numchecks) table_close(relrel, RowExclusiveLock); } +/* + * Check for references to generated columns + */ +static bool +check_nested_generated_walker(Node *node, void *context) +{ + ParseState *pstate = context; + + if (node == NULL) + return false; + else if (IsA(node, Var)) + { + Var *var = (Var *) node; + Oid relid; + AttrNumber attnum; + + relid = rt_fetch(var->varno, pstate->p_rtable)->relid; + attnum = var->varattno; + + if (OidIsValid(relid) && AttributeNumberIsValid(attnum) && get_attgenerated(relid, attnum)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot use generated column \"%s\" in column generation expression", + get_attname(relid, attnum, false)), + errdetail("A generated column cannot reference another generated column."), + parser_errposition(pstate, var->location))); + + return false; + } + else + return expression_tree_walker(node, check_nested_generated_walker, + (void *) context); +} + +static void +check_nested_generated(ParseState *pstate, Node *node) +{ + check_nested_generated_walker(node, pstate); +} + /* * Take a raw default and convert it to a cooked format ready for * storage. @@ -2927,7 +2997,8 @@ cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, int32 atttypmod, - const char *attname) + const char *attname, + char attgenerated) { Node *expr; @@ -2936,17 +3007,25 @@ cookDefault(ParseState *pstate, /* * Transform raw parsetree to executable expression. */ - expr = transformExpr(pstate, raw_default, EXPR_KIND_COLUMN_DEFAULT); + expr = transformExpr(pstate, raw_default, attgenerated ? EXPR_KIND_GENERATED_COLUMN : EXPR_KIND_COLUMN_DEFAULT); /* * Make sure default expr does not refer to any vars (we need this check * since the pstate includes the target table). */ - if (contain_var_clause(expr)) + if (!attgenerated && contain_var_clause(expr)) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), errmsg("cannot use column references in default expression"))); + if (attgenerated) + check_nested_generated(pstate, expr); + + if (attgenerated && contain_mutable_functions(expr)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("generation expression is not immutable"))); + /* * transformExpr() should have already rejected subqueries, aggregates, * window functions, and SRFs, based on the EXPR_KIND_ for a default diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql index 94e482596f..16677e78d6 100644 --- a/src/backend/catalog/information_schema.sql +++ b/src/backend/catalog/information_schema.sql @@ -509,7 +509,29 @@ CREATE VIEW collation_character_set_applicability AS * COLUMN_COLUMN_USAGE view */ --- feature not supported +CREATE VIEW column_column_usage AS + SELECT CAST(current_database() AS sql_identifier) AS table_catalog, + CAST(n.nspname AS sql_identifier) AS table_schema, + CAST(c.relname AS sql_identifier) AS table_name, + CAST(ac.attname AS sql_identifier) AS column_name, + CAST(ad.attname AS sql_identifier) AS dependent_column + + FROM pg_namespace n, pg_class c, pg_depend d, + pg_attribute ac, pg_attribute ad + + WHERE n.oid = c.relnamespace + AND c.oid = ac.attrelid + AND c.oid = ad.attrelid + AND d.classid = 'pg_catalog.pg_class'::regclass + AND d.refclassid = 'pg_catalog.pg_class'::regclass + AND d.objid = d.refobjid + AND c.oid = d.objid + AND d.objsubid = ad.attnum + AND d.refobjsubid = ac.attnum + AND ad.attgenerated <> '' + AND pg_has_role(c.relowner, 'USAGE'); + +GRANT SELECT ON column_column_usage TO PUBLIC; /* @@ -656,7 +678,7 @@ CREATE VIEW columns AS CAST(c.relname AS sql_identifier) AS table_name, CAST(a.attname AS sql_identifier) AS column_name, CAST(a.attnum AS cardinal_number) AS ordinal_position, - CAST(pg_get_expr(ad.adbin, ad.adrelid) AS character_data) AS column_default, + CAST(CASE WHEN a.attgenerated = '' THEN pg_get_expr(ad.adbin, ad.adrelid) END AS character_data) AS column_default, CAST(CASE WHEN a.attnotnull OR (t.typtype = 'd' AND t.typnotnull) THEN 'NO' ELSE 'YES' END AS yes_or_no) AS is_nullable, @@ -745,8 +767,8 @@ CREATE VIEW columns AS CAST(seq.seqmin AS character_data) AS identity_minimum, CAST(CASE WHEN seq.seqcycle THEN 'YES' ELSE 'NO' END AS yes_or_no) AS identity_cycle, - CAST('NEVER' AS character_data) AS is_generated, - CAST(null AS character_data) AS generation_expression, + CAST(CASE WHEN a.attgenerated <> '' THEN 'ALWAYS' ELSE 'NEVER' END AS character_data) AS is_generated, + CAST(CASE WHEN a.attgenerated <> '' THEN pg_get_expr(ad.adbin, ad.adrelid) END AS character_data) AS generation_expression, CAST(CASE WHEN c.relkind IN ('r', 'p') OR (c.relkind IN ('v', 'f') AND diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 705df8900b..27d3a012af 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -32,6 +32,7 @@ #include "commands/trigger.h" #include "executor/execPartition.h" #include "executor/executor.h" +#include "executor/nodeModifyTable.h" #include "executor/tuptable.h" #include "foreign/fdwapi.h" #include "libpq/libpq.h" @@ -2922,6 +2923,21 @@ CopyFrom(CopyState cstate) } else { + /* + * Compute stored generated columns + * + * Switch memory context so that the new tuple is in the same + * context as the old one. + */ + if (resultRelInfo->ri_RelationDesc->rd_att->constr && + resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored) + { + ExecComputeStoredGenerated(estate, slot); + MemoryContextSwitchTo(batchcontext); + tuple = ExecCopySlotHeapTuple(slot); + MemoryContextSwitchTo(oldcontext); + } + /* * If the target is a plain table, check the constraints of * the tuple. @@ -3271,7 +3287,7 @@ BeginCopyFrom(ParseState *pstate, fmgr_info(in_func_oid, &in_functions[attnum - 1]); /* Get default info if needed */ - if (!list_member_int(cstate->attnumlist, attnum)) + if (!list_member_int(cstate->attnumlist, attnum) && !att->attgenerated) { /* attribute is NOT to be copied from input */ /* use default value if one exists */ @@ -4876,6 +4892,11 @@ CopyAttributeOutCSV(CopyState cstate, char *string, * or NIL if there was none (in which case we want all the non-dropped * columns). * + * We don't include generated columns in the generated full list and we don't + * allow them to be specified explicitly. They don't make sense for COPY + * FROM, but we could possibly allow them for COPY TO. But this way it's at + * least ensured that whatever we copy out can be copied back in. + * * rel can be NULL ... it's only used for error reports. */ static List * @@ -4893,6 +4914,8 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist) { if (TupleDescAttr(tupDesc, i)->attisdropped) continue; + if (TupleDescAttr(tupDesc, i)->attgenerated) + continue; attnums = lappend_int(attnums, i + 1); } } @@ -4917,6 +4940,12 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist) continue; if (namestrcmp(&(att->attname), name) == 0) { + if (att->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("column \"%s\" is a generated column", + name), + errdetail("Generated columns cannot be used in COPY."))); attnum = att->attnum; break; } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 3183b2aaa1..80176cc9c3 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -760,6 +760,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, rawEnt->attnum = attnum; rawEnt->raw_default = colDef->raw_default; rawEnt->missingMode = false; + rawEnt->generated = colDef->generated; rawDefaults = lappend(rawDefaults, rawEnt); attr->atthasdef = true; } @@ -783,6 +784,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (colDef->identity) attr->attidentity = colDef->identity; + + if (colDef->generated) + attr->attgenerated = colDef->generated; } /* @@ -863,6 +867,27 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, */ rel = relation_open(relationId, AccessExclusiveLock); + /* + * Now add any newly specified column default and generation expressions + * to the new relation. These are passed to us in the form of raw + * parsetrees; we need to transform them to executable expression trees + * before they can be added. The most convenient way to do that is to + * apply the parser's transformExpr routine, but transformExpr doesn't + * work unless we have a pre-existing relation. So, the transformation has + * to be postponed to this final step of CREATE TABLE. + * + * This needs to be before processing the partitioning clauses because + * those could refer to generated columns. + */ + if (rawDefaults) + AddRelationNewConstraints(rel, rawDefaults, NIL, + true, true, false, queryString); + + /* + * Make column generation expressions visible for use by partitioning. + */ + CommandCounterIncrement(); + /* Process and store partition bound, if any. */ if (stmt->partbound) { @@ -1064,16 +1089,12 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, } /* - * Now add any newly specified column default values and CHECK constraints - * to the new relation. These are passed to us in the form of raw - * parsetrees; we need to transform them to executable expression trees - * before they can be added. The most convenient way to do that is to - * apply the parser's transformExpr routine, but transformExpr doesn't - * work unless we have a pre-existing relation. So, the transformation has - * to be postponed to this final step of CREATE TABLE. + * Now add any newly specified CHECK constraints to the new relation. + * Same as for defaults above, but these need to come after partitioning + * is set up. */ - if (rawDefaults || stmt->constraints) - AddRelationNewConstraints(rel, rawDefaults, stmt->constraints, + if (stmt->constraints) + AddRelationNewConstraints(rel, NIL, stmt->constraints, true, true, false, queryString); ObjectAddressSet(address, RelationRelationId, relationId); @@ -2232,6 +2253,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence, def->is_not_null |= attribute->attnotnull; /* Default and other constraints are handled below */ newattno[parent_attno - 1] = exist_attno; + + /* Check for GENERATED conflicts */ + if (def->generated != attribute->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("inherited column \"%s\" has a generation conflict", + attributeName))); } else { @@ -2249,6 +2277,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, def->storage = attribute->attstorage; def->raw_default = NULL; def->cooked_default = NULL; + def->generated = attribute->attgenerated; def->collClause = NULL; def->collOid = attribute->attcollation; def->constraints = NIL; @@ -5599,6 +5628,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, attribute.atthasdef = false; attribute.atthasmissing = false; attribute.attidentity = colDef->identity; + attribute.attgenerated = colDef->generated; attribute.attisdropped = false; attribute.attislocal = colDef->is_local; attribute.attinhcount = colDef->inhcount; @@ -5644,7 +5674,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, * DEFAULT value outside of the heap. This may be disabled inside * AddRelationNewConstraints if the optimization cannot be applied. */ - rawEnt->missingMode = true; + rawEnt->missingMode = (!colDef->generated); + + rawEnt->generated = colDef->generated; /* * This function is intended for CREATE TABLE, so it processes a @@ -6225,6 +6257,12 @@ ATExecColumnDefault(Relation rel, const char *colName, colName, RelationGetRelationName(rel)), newDefault ? 0 : errhint("Use ALTER TABLE ... ALTER COLUMN ... DROP IDENTITY instead."))); + if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("column \"%s\" of relation \"%s\" is a generated column", + colName, RelationGetRelationName(rel)))); + /* * Remove any old default for the column. We use RESTRICT here for * safety, but at present we do not expect anything to depend on the @@ -6246,6 +6284,7 @@ ATExecColumnDefault(Relation rel, const char *colName, rawEnt->attnum = attnum; rawEnt->raw_default = newDefault; rawEnt->missingMode = false; + rawEnt->generated = '\0'; /* * This function is intended for CREATE TABLE, so it processes a @@ -7546,6 +7585,32 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, */ checkFkeyPermissions(pkrel, pkattnum, numpks); + /* + * Check some things for generated columns. + */ + for (i = 0; i < numfks; i++) + { + char attgenerated = TupleDescAttr(RelationGetDescr(rel), fkattnum[i] - 1)->attgenerated; + + if (attgenerated) + { + /* + * Check restrictions on UPDATE/DELETE actions, per SQL standard + */ + if (fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETNULL || + fkconstraint->fk_upd_action == FKCONSTR_ACTION_SETDEFAULT || + fkconstraint->fk_upd_action == FKCONSTR_ACTION_CASCADE) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid ON UPDATE action for foreign key constraint containing generated column"))); + if (fkconstraint->fk_del_action == FKCONSTR_ACTION_SETNULL || + fkconstraint->fk_del_action == FKCONSTR_ACTION_SETDEFAULT) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid ON DELETE action for foreign key constraint containing generated column"))); + } + } + /* * Look up the equality operators to use in the constraint. * @@ -9937,10 +10002,18 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, COERCE_IMPLICIT_CAST, -1); if (defaultexpr == NULL) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("default for column \"%s\" cannot be cast automatically to type %s", - colName, format_type_be(targettype)))); + { + if (attTup->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("generation expression for column \"%s\" cannot be cast automatically to type %s", + colName, format_type_be(targettype)))); + else + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("default for column \"%s\" cannot be cast automatically to type %s", + colName, format_type_be(targettype)))); + } } else defaultexpr = NULL; @@ -10016,6 +10089,21 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, */ Assert(foundObject.objectSubId == 0); } + else if (relKind == RELKIND_RELATION && + foundObject.objectSubId != 0 && + get_attgenerated(foundObject.objectId, foundObject.objectSubId)) + { + /* + * Changing the type of a column that is used by a + * generated column is not allowed by SQL standard. + * It might be doable with some thinking and effort. + */ + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot alter type of a column used by a generated column"), + errdetail("Column \"%s\" is used by generated column \"%s\".", + colName, get_attname(foundObject.objectId, foundObject.objectSubId, false)))); + } else { /* Not expecting any other direct dependencies... */ @@ -10160,7 +10248,8 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, /* * Now scan for dependencies of this column on other things. The only * thing we should find is the dependency on the column datatype, which we - * want to remove, and possibly a collation dependency. + * want to remove, possibly a collation dependency, and dependencies on + * other columns if it is a generated column. */ ScanKeyInit(&key[0], Anum_pg_depend_classid, @@ -10181,15 +10270,26 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, while (HeapTupleIsValid(depTup = systable_getnext(scan))) { Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup); + ObjectAddress foundObject; - if (foundDep->deptype != DEPENDENCY_NORMAL) + foundObject.classId = foundDep->refclassid; + foundObject.objectId = foundDep->refobjid; + foundObject.objectSubId = foundDep->refobjsubid; + + if (foundDep->deptype != DEPENDENCY_NORMAL && + foundDep->deptype != DEPENDENCY_AUTO) elog(ERROR, "found unexpected dependency type '%c'", foundDep->deptype); if (!(foundDep->refclassid == TypeRelationId && foundDep->refobjid == attTup->atttypid) && !(foundDep->refclassid == CollationRelationId && - foundDep->refobjid == attTup->attcollation)) - elog(ERROR, "found unexpected dependency for column"); + foundDep->refobjid == attTup->attcollation) && + !(foundDep->refclassid == RelationRelationId && + foundDep->refobjid == RelationGetRelid(rel) && + foundDep->refobjsubid != 0) + ) + elog(ERROR, "found unexpected dependency for column: %s", + getObjectDescription(&foundObject)); CatalogTupleDelete(depRel, &depTup->t_self); } @@ -14321,6 +14421,18 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu pelem->name), parser_errposition(pstate, pelem->location))); + /* + * Generated columns cannot work: They are computed after BEFORE + * triggers, but partition routing is done before all triggers. + */ + if (attform->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot use generated column in partition key"), + errdetail("Column \"%s\" is a generated column.", + pelem->name), + parser_errposition(pstate, pelem->location))); + partattrs[attn] = attform->attnum; atttype = attform->atttypid; attcollation = attform->attcollation; @@ -14408,6 +14520,25 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu errmsg("partition key expressions cannot contain system column references"))); } + /* + * Generated columns cannot work: They are computed after + * BEFORE triggers, but partition routing is done before all + * triggers. + */ + i = -1; + while ((i = bms_next_member(expr_attrs, i)) >= 0) + { + AttrNumber attno = i + FirstLowInvalidHeapAttributeNumber; + + if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot use generated column in partition key"), + errdetail("Column \"%s\" is a generated column.", + get_attname(RelationGetRelid(rel), attno, false)), + parser_errposition(pstate, pelem->location))); + } + /* * While it is not exactly *wrong* for a partition expression * to be a constant, it seems better to reject such keys. diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index e03ffdde38..3ae2640abd 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -75,8 +75,9 @@ static int MyTriggerDepth = 0; * they use, so we let them be duplicated. Be sure to update all if one needs * to be changed, however. */ -#define GetUpdatedColumns(relinfo, estate) \ - (exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols) +#define GetAllUpdatedColumns(relinfo, estate) \ + (bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \ + exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols)) /* Local function prototypes */ static void ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid); @@ -640,6 +641,24 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("BEFORE trigger's WHEN condition cannot reference NEW system columns"), parser_errposition(pstate, var->location))); + if (TRIGGER_FOR_BEFORE(tgtype) && + var->varattno == 0 && + RelationGetDescr(rel)->constr && + RelationGetDescr(rel)->constr->has_generated_stored) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"), + errdetail("A whole-row reference is used and the table contains generated columns."), + parser_errposition(pstate, var->location))); + if (TRIGGER_FOR_BEFORE(tgtype) && + var->varattno > 0 && + TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BEFORE trigger's WHEN condition cannot reference NEW generated columns"), + errdetail("Column \"%s\" is a generated column.", + NameStr(TupleDescAttr(RelationGetDescr(rel), var->varattno - 1)->attname)), + parser_errposition(pstate, var->location))); break; default: /* can't happen without add_missing_from, so just elog */ @@ -2931,7 +2950,7 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo) CMD_UPDATE)) return; - updatedCols = GetUpdatedColumns(relinfo, estate); + updatedCols = GetAllUpdatedColumns(relinfo, estate); LocTriggerData.type = T_TriggerData; LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE | @@ -2980,7 +2999,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo, if (trigdesc && trigdesc->trig_update_after_statement) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, false, NULL, NULL, NIL, - GetUpdatedColumns(relinfo, estate), + GetAllUpdatedColumns(relinfo, estate), transition_capture); } @@ -3049,7 +3068,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, LocTriggerData.tg_relation = relinfo->ri_RelationDesc; LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_newtable = NULL; - updatedCols = GetUpdatedColumns(relinfo, estate); + updatedCols = GetAllUpdatedColumns(relinfo, estate); for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; @@ -3140,7 +3159,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, true, oldslot, newslot, recheckIndexes, - GetUpdatedColumns(relinfo, estate), + GetAllUpdatedColumns(relinfo, estate), transition_capture); } } diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index f94248dc95..7e6bcc5239 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -918,7 +918,8 @@ DefineDomain(CreateDomainStmt *stmt) defaultExpr = cookDefault(pstate, constr->raw_expr, basetypeoid, basetypeMod, - domainName); + domainName, + 0); /* * If the expression is just a NULL constant, we treat it @@ -2228,7 +2229,8 @@ AlterDomainDefault(List *names, Node *defaultRaw) defaultExpr = cookDefault(pstate, defaultRaw, typTup->typbasetype, typTup->typtypmod, - NameStr(typTup->typname)); + NameStr(typTup->typname), + 0); /* * If the expression is just a NULL constant, we treat the command diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 426686b6ef..03dcc7b820 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -102,7 +102,7 @@ static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree); /* - * Note that GetUpdatedColumns() also exists in commands/trigger.c. There does + * Note that GetAllUpdatedColumns() also exists in commands/trigger.c. There does * not appear to be any good header to put it into, given the structures that * it uses, so we let them be duplicated. Be sure to update both if one needs * to be changed, however. @@ -111,6 +111,9 @@ static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate, (exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->insertedCols) #define GetUpdatedColumns(relinfo, estate) \ (exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols) +#define GetAllUpdatedColumns(relinfo, estate) \ + (bms_union(exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->updatedCols, \ + exec_rt_fetch((relinfo)->ri_RangeTableIndex, estate)->extraUpdatedCols)) /* end of local decls */ @@ -1316,6 +1319,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, resultRelInfo->ri_FdwState = NULL; resultRelInfo->ri_usesFdwDirectModify = false; resultRelInfo->ri_ConstraintExprs = NULL; + resultRelInfo->ri_GeneratedExprs = NULL; resultRelInfo->ri_junkFilter = NULL; resultRelInfo->ri_projectReturning = NULL; resultRelInfo->ri_onConflictArbiterIndexes = NIL; @@ -2328,7 +2332,7 @@ ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo) * been modified, then we can use a weaker lock, allowing for better * concurrency. */ - updatedCols = GetUpdatedColumns(relinfo, estate); + updatedCols = GetAllUpdatedColumns(relinfo, estate); keyCols = RelationGetIndexAttrBitmap(relinfo->ri_RelationDesc, INDEX_ATTR_BITMAP_KEY); diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index d8b48c667c..f8f6463358 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -21,6 +21,7 @@ #include "access/xact.h" #include "commands/trigger.h" #include "executor/executor.h" +#include "executor/nodeModifyTable.h" #include "nodes/nodeFuncs.h" #include "parser/parse_relation.h" #include "parser/parsetree.h" @@ -412,6 +413,11 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) { List *recheckIndexes = NIL; + /* Compute stored generated columns */ + if (rel->rd_att->constr && + rel->rd_att->constr->has_generated_stored) + ExecComputeStoredGenerated(estate, slot); + /* Check the constraints of the tuple */ if (rel->rd_att->constr) ExecConstraints(resultRelInfo, slot, estate); @@ -473,6 +479,11 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, List *recheckIndexes = NIL; bool update_indexes; + /* Compute stored generated columns */ + if (rel->rd_att->constr && + rel->rd_att->constr->has_generated_stored) + ExecComputeStoredGenerated(estate, slot); + /* Check the constraints of the tuple */ if (rel->rd_att->constr) ExecConstraints(resultRelInfo, slot, estate); diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 7be0e7745a..61c4459f67 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -49,6 +49,7 @@ #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "rewrite/rewriteHandler.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" #include "utils/builtins.h" @@ -240,6 +241,89 @@ ExecCheckTIDVisible(EState *estate, ExecClearTuple(tempSlot); } +/* + * Compute stored generated columns for a tuple + */ +void +ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot) +{ + ResultRelInfo *resultRelInfo = estate->es_result_relation_info; + Relation rel = resultRelInfo->ri_RelationDesc; + TupleDesc tupdesc = RelationGetDescr(rel); + int natts = tupdesc->natts; + MemoryContext oldContext; + Datum *values; + bool *nulls; + bool *replaces; + HeapTuple oldtuple, newtuple; + bool should_free; + + Assert(tupdesc->constr && tupdesc->constr->has_generated_stored); + + /* + * If first time through for this result relation, build expression + * nodetrees for rel's stored generation expressions. Keep them in the + * per-query memory context so they'll survive throughout the query. + */ + if (resultRelInfo->ri_GeneratedExprs == NULL) + { + oldContext = MemoryContextSwitchTo(estate->es_query_cxt); + + resultRelInfo->ri_GeneratedExprs = + (ExprState **) palloc(natts * sizeof(ExprState *)); + + for (int i = 0; i < natts; i++) + { + if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED) + { + Expr *expr; + + expr = (Expr *) build_column_default(rel, i + 1); + if (expr == NULL) + elog(ERROR, "no generation expression found for column number %d of table \"%s\"", + i + 1, RelationGetRelationName(rel)); + + resultRelInfo->ri_GeneratedExprs[i] = ExecPrepareExpr(expr, estate); + } + } + + MemoryContextSwitchTo(oldContext); + } + + oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); + + values = palloc(sizeof(*values) * natts); + nulls = palloc(sizeof(*nulls) * natts); + replaces = palloc0(sizeof(*replaces) * natts); + + for (int i = 0; i < natts; i++) + { + if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED) + { + ExprContext *econtext; + Datum val; + bool isnull; + + econtext = GetPerTupleExprContext(estate); + econtext->ecxt_scantuple = slot; + + val = ExecEvalExpr(resultRelInfo->ri_GeneratedExprs[i], econtext, &isnull); + + values[i] = val; + nulls[i] = isnull; + replaces[i] = true; + } + } + + oldtuple = ExecFetchSlotHeapTuple(slot, true, &should_free); + newtuple = heap_modify_tuple(oldtuple, tupdesc, values, nulls, replaces); + ExecForceStoreHeapTuple(newtuple, slot); + if (should_free) + heap_freetuple(oldtuple); + + MemoryContextSwitchTo(oldContext); +} + /* ---------------------------------------------------------------- * ExecInsert * @@ -297,6 +381,13 @@ ExecInsert(ModifyTableState *mtstate, } else if (resultRelInfo->ri_FdwRoutine) { + /* + * Compute stored generated columns + */ + if (resultRelationDesc->rd_att->constr && + resultRelationDesc->rd_att->constr->has_generated_stored) + ExecComputeStoredGenerated(estate, slot); + /* * insert into foreign table: let the FDW do it */ @@ -326,6 +417,13 @@ ExecInsert(ModifyTableState *mtstate, */ slot->tts_tableOid = RelationGetRelid(resultRelationDesc); + /* + * Compute stored generated columns + */ + if (resultRelationDesc->rd_att->constr && + resultRelationDesc->rd_att->constr->has_generated_stored) + ExecComputeStoredGenerated(estate, slot); + /* * Check any RLS WITH CHECK policies. * @@ -964,6 +1062,13 @@ ExecUpdate(ModifyTableState *mtstate, } else if (resultRelInfo->ri_FdwRoutine) { + /* + * Compute stored generated columns + */ + if (resultRelationDesc->rd_att->constr && + resultRelationDesc->rd_att->constr->has_generated_stored) + ExecComputeStoredGenerated(estate, slot); + /* * update in foreign table: let the FDW do it */ @@ -994,6 +1099,13 @@ ExecUpdate(ModifyTableState *mtstate, */ slot->tts_tableOid = RelationGetRelid(resultRelationDesc); + /* + * Compute stored generated columns + */ + if (resultRelationDesc->rd_att->constr && + resultRelationDesc->rd_att->constr->has_generated_stored) + ExecComputeStoredGenerated(estate, slot); + /* * Check any RLS UPDATE WITH CHECK policies * diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 04cc15606d..2978e822dc 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2390,6 +2390,7 @@ _copyRangeTblEntry(const RangeTblEntry *from) COPY_BITMAPSET_FIELD(selectedCols); COPY_BITMAPSET_FIELD(insertedCols); COPY_BITMAPSET_FIELD(updatedCols); + COPY_BITMAPSET_FIELD(extraUpdatedCols); COPY_NODE_FIELD(securityQuals); return newnode; @@ -2888,6 +2889,7 @@ _copyColumnDef(const ColumnDef *from) COPY_NODE_FIELD(cooked_default); COPY_SCALAR_FIELD(identity); COPY_NODE_FIELD(identitySequence); + COPY_SCALAR_FIELD(generated); COPY_NODE_FIELD(collClause); COPY_SCALAR_FIELD(collOid); COPY_NODE_FIELD(constraints); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 91c007ad5b..b11fcb9def 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2564,6 +2564,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b) COMPARE_NODE_FIELD(cooked_default); COMPARE_SCALAR_FIELD(identity); COMPARE_NODE_FIELD(identitySequence); + COMPARE_SCALAR_FIELD(generated); COMPARE_NODE_FIELD(collClause); COMPARE_SCALAR_FIELD(collOid); COMPARE_NODE_FIELD(constraints); @@ -2663,6 +2664,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b) COMPARE_BITMAPSET_FIELD(selectedCols); COMPARE_BITMAPSET_FIELD(insertedCols); COMPARE_BITMAPSET_FIELD(updatedCols); + COMPARE_BITMAPSET_FIELD(extraUpdatedCols); COMPARE_NODE_FIELD(securityQuals); return true; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 910a738c20..3282be0e4b 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2792,6 +2792,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node) WRITE_NODE_FIELD(cooked_default); WRITE_CHAR_FIELD(identity); WRITE_NODE_FIELD(identitySequence); + WRITE_CHAR_FIELD(generated); WRITE_NODE_FIELD(collClause); WRITE_OID_FIELD(collOid); WRITE_NODE_FIELD(constraints); @@ -3096,6 +3097,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node) WRITE_BITMAPSET_FIELD(selectedCols); WRITE_BITMAPSET_FIELD(insertedCols); WRITE_BITMAPSET_FIELD(updatedCols); + WRITE_BITMAPSET_FIELD(extraUpdatedCols); WRITE_NODE_FIELD(securityQuals); } @@ -3467,6 +3469,13 @@ _outConstraint(StringInfo str, const Constraint *node) WRITE_CHAR_FIELD(generated_when); break; + case CONSTR_GENERATED: + appendStringInfoString(str, "GENERATED"); + WRITE_NODE_FIELD(raw_expr); + WRITE_STRING_FIELD(cooked_expr); + WRITE_CHAR_FIELD(generated_when); + break; + case CONSTR_CHECK: appendStringInfoString(str, "CHECK"); WRITE_BOOL_FIELD(is_no_inherit); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index eff98febf1..3b96492b36 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1430,6 +1430,7 @@ _readRangeTblEntry(void) READ_BITMAPSET_FIELD(selectedCols); READ_BITMAPSET_FIELD(insertedCols); READ_BITMAPSET_FIELD(updatedCols); + READ_BITMAPSET_FIELD(extraUpdatedCols); READ_NODE_FIELD(securityQuals); READ_DONE(); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 979c3c212f..cc222cb06c 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -6570,8 +6570,9 @@ make_modifytable(PlannerInfo *root, /* * Try to modify the foreign table directly if (1) the FDW provides - * callback functions needed for that, (2) there are no row-level - * triggers on the foreign table, and (3) there are no WITH CHECK + * callback functions needed for that and (2) there are no local + * structures that need to be run for each modified row: row-level + * triggers on the foreign table, stored generated columns, WITH CHECK * OPTIONs from parent views. */ direct_modify = false; @@ -6581,7 +6582,8 @@ make_modifytable(PlannerInfo *root, fdwroutine->IterateDirectModify != NULL && fdwroutine->EndDirectModify != NULL && withCheckOptionLists == NIL && - !has_row_triggers(subroot, rti, operation)) + !has_row_triggers(subroot, rti, operation) && + !has_stored_generated_columns(subroot, rti)) direct_modify = fdwroutine->PlanDirectModify(subroot, node, rti, i); if (direct_modify) direct_modify_plans = bms_add_member(direct_modify_plans, i); diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c index 1fa154e0cb..a811f6a547 100644 --- a/src/backend/optimizer/util/inherit.c +++ b/src/backend/optimizer/util/inherit.c @@ -272,6 +272,10 @@ expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *parentrte, if (!root->partColsUpdated) root->partColsUpdated = has_partition_attrs(parentrel, parentrte->updatedCols, NULL); + /* + * There shouldn't be any generated columns in the partition key. + */ + Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL)); /* First expand the partitioned table itself. */ expand_single_inheritance_child(root, parentrte, parentRTindex, parentrel, @@ -412,6 +416,8 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte, appinfo->translated_vars); childrte->updatedCols = translate_col_privs(parentrte->updatedCols, appinfo->translated_vars); + childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols, + appinfo->translated_vars); } } diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 702c4f89b8..335c859154 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -2071,6 +2071,25 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event) return result; } +bool +has_stored_generated_columns(PlannerInfo *root, Index rti) +{ + RangeTblEntry *rte = planner_rt_fetch(rti, root); + Relation relation; + TupleDesc tupdesc; + bool result = false; + + /* Assume we already have adequate lock */ + relation = heap_open(rte->relid, NoLock); + + tupdesc = RelationGetDescr(relation); + result = tupdesc->constr && tupdesc->constr->has_generated_stored; + + heap_close(relation, NoLock); + + return result; +} + /* * set_relation_partition_info * diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index d6cdd16607..400558b552 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -2287,6 +2287,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist) RangeTblEntry *target_rte; ListCell *orig_tl; ListCell *tl; + TupleDesc tupdesc = pstate->p_target_relation->rd_att; tlist = transformTargetList(pstate, origTlist, EXPR_KIND_UPDATE_SOURCE); @@ -2345,6 +2346,32 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist) if (orig_tl != NULL) elog(ERROR, "UPDATE target count mismatch --- internal error"); + /* + * Record in extraUpdatedCols generated columns referencing updated base + * columns. + */ + if (tupdesc->constr && + tupdesc->constr->has_generated_stored) + { + for (int i = 0; i < tupdesc->constr->num_defval; i++) + { + AttrDefault defval = tupdesc->constr->defval[i]; + Node *expr; + Bitmapset *attrs_used = NULL; + + /* skip if not generated column */ + if (!TupleDescAttr(tupdesc, defval.adnum - 1)->attgenerated) + continue; + + expr = stringToNode(defval.adbin); + pull_varattnos(expr, 1, &attrs_used); + + if (bms_overlap(target_rte->updatedCols, attrs_used)) + target_rte->extraUpdatedCols = bms_add_member(target_rte->extraUpdatedCols, + defval.adnum - FirstLowInvalidHeapAttributeNumber); + } + } + return tlist; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 0a4822829a..f6d890cfef 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -680,7 +680,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P - START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P + START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN @@ -3496,6 +3496,16 @@ ColConstraintElem: n->location = @1; $$ = (Node *)n; } + | GENERATED generated_when AS '(' a_expr ')' STORED + { + Constraint *n = makeNode(Constraint); + n->contype = CONSTR_GENERATED; + n->generated_when = $2; + n->raw_expr = $5; + n->cooked_expr = NULL; + n->location = @1; + $$ = (Node *)n; + } | REFERENCES qualified_name opt_column_list key_match key_actions { Constraint *n = makeNode(Constraint); @@ -3586,6 +3596,7 @@ TableLikeOption: | CONSTRAINTS { $$ = CREATE_TABLE_LIKE_CONSTRAINTS; } | DEFAULTS { $$ = CREATE_TABLE_LIKE_DEFAULTS; } | IDENTITY_P { $$ = CREATE_TABLE_LIKE_IDENTITY; } + | GENERATED { $$ = CREATE_TABLE_LIKE_GENERATED; } | INDEXES { $$ = CREATE_TABLE_LIKE_INDEXES; } | STATISTICS { $$ = CREATE_TABLE_LIKE_STATISTICS; } | STORAGE { $$ = CREATE_TABLE_LIKE_STORAGE; } @@ -15224,6 +15235,7 @@ unreserved_keyword: | STDIN | STDOUT | STORAGE + | STORED | STRICT_P | STRIP_P | SUBSCRIPTION diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 183ea0f2c4..c745fcdd2b 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -520,6 +520,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) err = _("grouping operations are not allowed in partition key expressions"); break; + case EXPR_KIND_GENERATED_COLUMN: + + if (isAgg) + err = _("aggregate functions are not allowed in column generation expressions"); + else + err = _("grouping operations are not allowed in column generation expressions"); + + break; case EXPR_KIND_CALL_ARGUMENT: if (isAgg) @@ -922,6 +930,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, case EXPR_KIND_COPY_WHERE: err = _("window functions are not allowed in COPY FROM WHERE conditions"); break; + case EXPR_KIND_GENERATED_COLUMN: + err = _("window functions are not allowed in column generation expressions"); + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index e559353529..0c88d0b2ee 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -1854,6 +1854,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_COPY_WHERE: err = _("cannot use subquery in COPY FROM WHERE condition"); break; + case EXPR_KIND_GENERATED_COLUMN: + err = _("cannot use subquery in column generation expression"); + break; /* * There is intentionally no default: case here, so that the @@ -3484,6 +3487,8 @@ ParseExprKindName(ParseExprKind exprKind) return "CALL"; case EXPR_KIND_COPY_WHERE: return "WHERE"; + case EXPR_KIND_GENERATED_COLUMN: + return "GENERATED AS"; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index b8447771bd..752cf1b315 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2526,6 +2526,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) case EXPR_KIND_COPY_WHERE: err = _("set-returning functions are not allowed in COPY FROM WHERE conditions"); break; + case EXPR_KIND_GENERATED_COLUMN: + err = _("set-returning functions are not allowed in column generation expressions"); + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index f3b6d193aa..0640d11fac 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -731,6 +731,17 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, const char *colname, colname), parser_errposition(pstate, location))); + /* + * In generated column, no system column is allowed except tableOid. + */ + if (pstate->p_expr_kind == EXPR_KIND_GENERATED_COLUMN && + attnum < InvalidAttrNumber && attnum != TableOidAttributeNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("cannot use system column \"%s\" in column generation expression", + colname), + parser_errposition(pstate, location))); + if (attnum != InvalidAttrNumber) { /* now check to see if column actually is defined */ @@ -1257,6 +1268,7 @@ addRangeTableEntry(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1328,6 +1340,7 @@ addRangeTableEntryForRelation(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1407,6 +1420,7 @@ addRangeTableEntryForSubquery(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1670,6 +1684,7 @@ addRangeTableEntryForFunction(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1733,6 +1748,7 @@ addRangeTableEntryForTableFunc(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1811,6 +1827,7 @@ addRangeTableEntryForValues(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1881,6 +1898,7 @@ addRangeTableEntryForJoin(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list @@ -1983,6 +2001,7 @@ addRangeTableEntryForCTE(ParseState *pstate, rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; /* * Add completed RTE to pstate's range table list, but not to join list diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index b4ec96d6d6..f8b4910509 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -502,6 +502,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) bool saw_nullable; bool saw_default; bool saw_identity; + bool saw_generated; ListCell *clist; cxt->columns = lappend(cxt->columns, column); @@ -609,6 +610,7 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) saw_nullable = false; saw_default = false; saw_identity = false; + saw_generated = false; foreach(clist, column->constraints) { @@ -689,6 +691,29 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) break; } + case CONSTR_GENERATED: + if (cxt->ofType) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("generated columns are not supported on typed tables"))); + if (cxt->partbound) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("generated columns are not supported on partitions"))); + + if (saw_generated) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("multiple generation clauses specified for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); + column->generated = ATTRIBUTE_GENERATED_STORED; + column->raw_default = constraint->raw_expr; + Assert(constraint->cooked_expr == NULL); + saw_generated = true; + break; + case CONSTR_CHECK: cxt->ckconstraints = lappend(cxt->ckconstraints, constraint); break; @@ -755,6 +780,22 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) column->colname, cxt->relation->relname), parser_errposition(cxt->pstate, constraint->location))); + + if (saw_default && saw_generated) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("both default and generation expression specified for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); + + if (saw_identity && saw_generated) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("both identity and generation expression specified for column \"%s\" of table \"%s\"", + column->colname, cxt->relation->relname), + parser_errposition(cxt->pstate, + constraint->location))); } /* @@ -983,11 +1024,13 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla * Copy default, if present and the default has been requested */ if (attribute->atthasdef && - (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS)) + (table_like_clause->options & CREATE_TABLE_LIKE_DEFAULTS || + table_like_clause->options & CREATE_TABLE_LIKE_GENERATED)) { Node *this_default = NULL; AttrDefault *attrdef; int i; + bool found_whole_row; /* Find default in constraint structure */ Assert(constr != NULL); @@ -1002,12 +1045,27 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla } Assert(this_default != NULL); + def->cooked_default = map_variable_attnos(this_default, + 1, 0, + attmap, tupleDesc->natts, + InvalidOid, &found_whole_row); + /* - * If default expr could contain any vars, we'd need to fix 'em, - * but it can't; so default is ready to apply to child. + * Prevent this for the same reason as for constraints below. + * Note that defaults cannot contain any vars, so it's OK that the + * error message refers to generated columns. */ + if (found_whole_row) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert whole-row table reference"), + errdetail("Generation expression for column \"%s\" contains a whole-row reference to table \"%s\".", + attributeName, + RelationGetRelationName(relation)))); - def->cooked_default = this_default; + if (attribute->attgenerated && + (table_like_clause->options & CREATE_TABLE_LIKE_GENERATED)) + def->generated = attribute->attgenerated; } /* diff --git a/src/backend/replication/logical/proto.c b/src/backend/replication/logical/proto.c index dffb6cd9fd..0411963f93 100644 --- a/src/backend/replication/logical/proto.c +++ b/src/backend/replication/logical/proto.c @@ -453,7 +453,7 @@ logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple) for (i = 0; i < desc->natts; i++) { - if (TupleDescAttr(desc, i)->attisdropped) + if (TupleDescAttr(desc, i)->attisdropped || TupleDescAttr(desc, i)->attgenerated) continue; nliveatts++; } @@ -473,8 +473,7 @@ logicalrep_write_tuple(StringInfo out, Relation rel, HeapTuple tuple) Form_pg_attribute att = TupleDescAttr(desc, i); char *outputstr; - /* skip dropped columns */ - if (att->attisdropped) + if (att->attisdropped || att->attgenerated) continue; if (isnull[i]) @@ -573,7 +572,7 @@ logicalrep_write_attrs(StringInfo out, Relation rel) /* send number of live attributes */ for (i = 0; i < desc->natts; i++) { - if (TupleDescAttr(desc, i)->attisdropped) + if (TupleDescAttr(desc, i)->attisdropped || TupleDescAttr(desc, i)->attgenerated) continue; nliveatts++; } @@ -591,7 +590,7 @@ logicalrep_write_attrs(StringInfo out, Relation rel) Form_pg_attribute att = TupleDescAttr(desc, i); uint8 flags = 0; - if (att->attisdropped) + if (att->attisdropped || att->attgenerated) continue; /* REPLICA IDENTITY FULL means all columns are sent as part of key. */ diff --git a/src/backend/replication/logical/relation.c b/src/backend/replication/logical/relation.c index 1d918d2c42..5aee4b80e6 100644 --- a/src/backend/replication/logical/relation.c +++ b/src/backend/replication/logical/relation.c @@ -276,7 +276,7 @@ logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode) int attnum; Form_pg_attribute attr = TupleDescAttr(desc, i); - if (attr->attisdropped) + if (attr->attisdropped || attr->attgenerated) { entry->attrmap[i] = -1; continue; diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c index 28f5fc23aa..7881079e96 100644 --- a/src/backend/replication/logical/tablesync.c +++ b/src/backend/replication/logical/tablesync.c @@ -697,10 +697,12 @@ fetch_remote_table_info(char *nspname, char *relname, " LEFT JOIN pg_catalog.pg_index i" " ON (i.indexrelid = pg_get_replica_identity_index(%u))" " WHERE a.attnum > 0::pg_catalog.int2" - " AND NOT a.attisdropped" + " AND NOT a.attisdropped %s" " AND a.attrelid = %u" " ORDER BY a.attnum", - lrel->remoteid, lrel->remoteid); + lrel->remoteid, + (walrcv_server_version(wrconn) >= 120000 ? "AND a.attgenerated = ''" : ""), + lrel->remoteid); res = walrcv_exec(wrconn, cmd.data, 4, attrRow); if (res->status != WALRCV_OK_TUPLES) diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index 52a5090b69..43edfef089 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -236,7 +236,7 @@ slot_fill_defaults(LogicalRepRelMapEntry *rel, EState *estate, { Expr *defexpr; - if (TupleDescAttr(desc, attnum)->attisdropped) + if (TupleDescAttr(desc, attnum)->attisdropped || TupleDescAttr(desc, attnum)->attgenerated) continue; if (rel->attrmap[attnum] >= 0) diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c index 5511957516..bf64c8e4a4 100644 --- a/src/backend/replication/pgoutput/pgoutput.c +++ b/src/backend/replication/pgoutput/pgoutput.c @@ -276,7 +276,7 @@ maybe_send_schema(LogicalDecodingContext *ctx, { Form_pg_attribute att = TupleDescAttr(desc, i); - if (att->attisdropped) + if (att->attisdropped || att->attgenerated) continue; if (att->atttypid < FirstNormalObjectId) diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 4fc50c89b9..39080776b0 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -818,6 +818,13 @@ rewriteTargetListIU(List *targetList, if (att_tup->attidentity == ATTRIBUTE_IDENTITY_BY_DEFAULT && override == OVERRIDING_USER_VALUE) apply_default = true; + + if (att_tup->attgenerated && !apply_default) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot insert into column \"%s\"", NameStr(att_tup->attname)), + errdetail("Column \"%s\" is a generated column.", + NameStr(att_tup->attname)))); } if (commandType == CMD_UPDATE) @@ -828,9 +835,23 @@ rewriteTargetListIU(List *targetList, errmsg("column \"%s\" can only be updated to DEFAULT", NameStr(att_tup->attname)), errdetail("Column \"%s\" is an identity column defined as GENERATED ALWAYS.", NameStr(att_tup->attname)))); + + if (att_tup->attgenerated && new_tle && !apply_default) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("column \"%s\" can only be updated to DEFAULT", NameStr(att_tup->attname)), + errdetail("Column \"%s\" is a generated column.", + NameStr(att_tup->attname)))); } - if (apply_default) + if (att_tup->attgenerated) + { + /* + * stored generated column will be fixed in executor + */ + new_tle = NULL; + } + else if (apply_default) { Node *new_expr; @@ -1137,13 +1158,12 @@ build_column_default(Relation rel, int attrno) } } - if (expr == NULL) - { - /* - * No per-column default, so look for a default for the type itself. - */ + /* + * No per-column default, so look for a default for the type itself. But + * not for generated columns. + */ + if (expr == NULL && !att_tup->attgenerated) expr = get_typdefault(atttype); - } if (expr == NULL) return NULL; /* No default anywhere */ @@ -1720,12 +1740,14 @@ ApplyRetrieveRule(Query *parsetree, subrte->selectedCols = rte->selectedCols; subrte->insertedCols = rte->insertedCols; subrte->updatedCols = rte->updatedCols; + subrte->extraUpdatedCols = rte->extraUpdatedCols; rte->requiredPerms = 0; /* no permission check on subquery itself */ rte->checkAsUser = InvalidOid; rte->selectedCols = NULL; rte->insertedCols = NULL; rte->updatedCols = NULL; + rte->extraUpdatedCols = NULL; return parsetree; } diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 59e6bcd856..10895567c0 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -821,6 +821,39 @@ get_attnum(Oid relid, const char *attname) return InvalidAttrNumber; } +/* + * get_attgenerated + * + * Given the relation id and the attribute name, + * return the "attgenerated" field from the attribute relation. + * + * Errors if not found. + * + * Since not generated is represented by '\0', this can also be used as a + * Boolean test. + */ +char +get_attgenerated(Oid relid, AttrNumber attnum) +{ + HeapTuple tp; + + tp = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum)); + if (HeapTupleIsValid(tp)) + { + Form_pg_attribute att_tup = (Form_pg_attribute) GETSTRUCT(tp); + char result; + + result = att_tup->attgenerated; + ReleaseSysCache(tp); + return result; + } + else + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, relid); +} + /* * get_atttype * diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c index 2b55f25e75..8f43d682cf 100644 --- a/src/backend/utils/cache/partcache.c +++ b/src/backend/utils/cache/partcache.c @@ -27,6 +27,7 @@ #include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" #include "partitioning/partbounds.h" +#include "rewrite/rewriteHandler.h" #include "utils/builtins.h" #include "utils/datum.h" #include "utils/lsyscache.h" diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 84609e0725..4557164b00 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -515,6 +515,7 @@ RelationBuildTupleDesc(Relation relation) constr = (TupleConstr *) MemoryContextAlloc(CacheMemoryContext, sizeof(TupleConstr)); constr->has_not_null = false; + constr->has_generated_stored = false; /* * Form a scan key that selects only user attributes (attnum > 0). @@ -567,6 +568,8 @@ RelationBuildTupleDesc(Relation relation) /* Update constraint/default info */ if (attp->attnotnull) constr->has_not_null = true; + if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED) + constr->has_generated_stored = true; /* If the column has a default, fill it into the attrdef array */ if (attp->atthasdef) @@ -3281,6 +3284,7 @@ RelationBuildLocalRelation(const char *relname, Form_pg_attribute datt = TupleDescAttr(rel->rd_att, i); datt->attidentity = satt->attidentity; + datt->attgenerated = satt->attgenerated; datt->attnotnull = satt->attnotnull; has_not_null |= satt->attnotnull; } diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 63699932c1..2a9e8538c6 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -2051,6 +2051,11 @@ dumpTableData_insert(Archive *fout, void *dcontext) { if (field > 0) archputs(", ", fout); + if (tbinfo->attgenerated[field]) + { + archputs("DEFAULT", fout); + continue; + } if (PQgetisnull(res, tuple, field)) { archputs("NULL", fout); @@ -8219,6 +8224,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) int i_attnotnull; int i_atthasdef; int i_attidentity; + int i_attgenerated; int i_attisdropped; int i_attlen; int i_attalign; @@ -8272,6 +8278,13 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) "a.attislocal,\n" "pg_catalog.format_type(t.oid, a.atttypmod) AS atttypname,\n"); + if (fout->remoteVersion >= 120000) + appendPQExpBuffer(q, + "a.attgenerated,\n"); + else + appendPQExpBuffer(q, + "'' AS attgenerated,\n"); + if (fout->remoteVersion >= 110000) appendPQExpBuffer(q, "CASE WHEN a.atthasmissing AND NOT a.attisdropped " @@ -8344,6 +8357,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) i_attnotnull = PQfnumber(res, "attnotnull"); i_atthasdef = PQfnumber(res, "atthasdef"); i_attidentity = PQfnumber(res, "attidentity"); + i_attgenerated = PQfnumber(res, "attgenerated"); i_attisdropped = PQfnumber(res, "attisdropped"); i_attlen = PQfnumber(res, "attlen"); i_attalign = PQfnumber(res, "attalign"); @@ -8361,6 +8375,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) tbinfo->attstorage = (char *) pg_malloc(ntups * sizeof(char)); tbinfo->typstorage = (char *) pg_malloc(ntups * sizeof(char)); tbinfo->attidentity = (char *) pg_malloc(ntups * sizeof(char)); + tbinfo->attgenerated = (char *) pg_malloc(ntups * sizeof(char)); tbinfo->attisdropped = (bool *) pg_malloc(ntups * sizeof(bool)); tbinfo->attlen = (int *) pg_malloc(ntups * sizeof(int)); tbinfo->attalign = (char *) pg_malloc(ntups * sizeof(char)); @@ -8387,6 +8402,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) tbinfo->attstorage[j] = *(PQgetvalue(res, j, i_attstorage)); tbinfo->typstorage[j] = *(PQgetvalue(res, j, i_typstorage)); tbinfo->attidentity[j] = *(PQgetvalue(res, j, i_attidentity)); + tbinfo->attgenerated[j] = *(PQgetvalue(res, j, i_attgenerated)); tbinfo->needs_override = tbinfo->needs_override || (tbinfo->attidentity[j] == ATTRIBUTE_IDENTITY_ALWAYS); tbinfo->attisdropped[j] = (PQgetvalue(res, j, i_attisdropped)[0] == 't'); tbinfo->attlen[j] = atoi(PQgetvalue(res, j, i_attlen)); @@ -15708,6 +15724,20 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) tbinfo->atttypnames[j]); } + if (has_default) + { + if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED) + appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED", + tbinfo->attrdefs[j]->adef_expr); + else + appendPQExpBuffer(q, " DEFAULT %s", + tbinfo->attrdefs[j]->adef_expr); + } + + + if (has_notnull) + appendPQExpBufferStr(q, " NOT NULL"); + /* Add collation if not default for the type */ if (OidIsValid(tbinfo->attcollation[j])) { @@ -15718,13 +15748,6 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) appendPQExpBuffer(q, " COLLATE %s", fmtQualifiedDumpable(coll)); } - - if (has_default) - appendPQExpBuffer(q, " DEFAULT %s", - tbinfo->attrdefs[j]->adef_expr); - - if (has_notnull) - appendPQExpBufferStr(q, " NOT NULL"); } } @@ -18303,6 +18326,7 @@ fmtCopyColumnList(const TableInfo *ti, PQExpBuffer buffer) int numatts = ti->numatts; char **attnames = ti->attnames; bool *attisdropped = ti->attisdropped; + char *attgenerated = ti->attgenerated; bool needComma; int i; @@ -18312,6 +18336,8 @@ fmtCopyColumnList(const TableInfo *ti, PQExpBuffer buffer) { if (attisdropped[i]) continue; + if (attgenerated[i]) + continue; if (needComma) appendPQExpBufferStr(buffer, ", "); appendPQExpBufferStr(buffer, fmtId(attnames[i])); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 2e1b90acd0..a72e3eb27c 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -310,6 +310,7 @@ typedef struct _tableInfo char *typstorage; /* type storage scheme */ bool *attisdropped; /* true if attr is dropped; don't dump it */ char *attidentity; + char *attgenerated; int *attlen; /* attribute length, used by binary_upgrade */ char *attalign; /* attribute align, used by binary_upgrade */ bool *attislocal; /* true if attr has local definition */ diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index bb128c89f3..243dca7264 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -1108,6 +1108,16 @@ repairDependencyLoop(DumpableObject **loop, } } + /* Loop of table with itself, happens with generated columns */ + if (nLoop == 1) + { + if (loop[0]->objType == DO_TABLE) + { + removeObjectDependency(loop[0], loop[0]->dumpId); + return; + } + } + /* * If all the objects are TABLE_DATA items, what we must have is a * circular set of foreign key constraints (or a single self-referential diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index de6895122e..a69375056d 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2392,6 +2392,23 @@ unlike => { exclude_dump_test_schema => 1, }, }, + 'CREATE TABLE test_table_generated' => { + create_order => 3, + create_sql => 'CREATE TABLE dump_test.test_table_generated ( + col1 int primary key, + col2 int generated always as (col1 * 2) stored + );', + regexp => qr/^ + \QCREATE TABLE dump_test.test_table_generated (\E\n + \s+\Qcol1 integer NOT NULL,\E\n + \s+\Qcol2 integer GENERATED ALWAYS AS ((col1 * 2)) STORED\E\n + \); + /xms, + like => + { %full_runs, %dump_test_schema_runs, section_pre_data => 1, }, + unlike => { exclude_dump_test_schema => 1, }, + }, + 'CREATE TABLE table_with_stats' => { create_order => 98, create_sql => 'CREATE TABLE dump_test.table_index_stats ( diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index fd8ebee8cd..1c9f20a576 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1462,6 +1462,7 @@ describeOneTableDetails(const char *schemaname, attnotnull_col = -1, attcoll_col = -1, attidentity_col = -1, + attgenerated_col = -1, isindexkey_col = -1, indexdef_col = -1, fdwopts_col = -1, @@ -1813,8 +1814,9 @@ describeOneTableDetails(const char *schemaname, if (show_column_details) { + /* use "pretty" mode for expression to avoid excessive parentheses */ appendPQExpBufferStr(&buf, - ",\n (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128)" + ",\n (SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid, true) for 128)" "\n FROM pg_catalog.pg_attrdef d" "\n WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef)" ",\n a.attnotnull"); @@ -1831,6 +1833,11 @@ describeOneTableDetails(const char *schemaname, else appendPQExpBufferStr(&buf, ",\n ''::pg_catalog.char AS attidentity"); attidentity_col = cols++; + if (pset.sversion >= 120000) + appendPQExpBufferStr(&buf, ",\n a.attgenerated"); + else + appendPQExpBufferStr(&buf, ",\n ''::pg_catalog.char AS attgenerated"); + attgenerated_col = cols++; } if (tableinfo.relkind == RELKIND_INDEX || tableinfo.relkind == RELKIND_PARTITIONED_INDEX) @@ -2011,6 +2018,7 @@ describeOneTableDetails(const char *schemaname, if (show_column_details) { char *identity; + char *generated; char *default_str = ""; printTableAddCell(&cont, PQgetvalue(res, i, attcoll_col), false, false); @@ -2020,16 +2028,19 @@ describeOneTableDetails(const char *schemaname, false, false); identity = PQgetvalue(res, i, attidentity_col); + generated = PQgetvalue(res, i, attgenerated_col); - if (!identity[0]) - /* (note: above we cut off the 'default' string at 128) */ - default_str = PQgetvalue(res, i, attrdef_col); - else if (identity[0] == ATTRIBUTE_IDENTITY_ALWAYS) + if (identity[0] == ATTRIBUTE_IDENTITY_ALWAYS) default_str = "generated always as identity"; else if (identity[0] == ATTRIBUTE_IDENTITY_BY_DEFAULT) default_str = "generated by default as identity"; + else if (generated[0] == ATTRIBUTE_GENERATED_STORED) + default_str = psprintf("generated always as (%s) stored", PQgetvalue(res, i, attrdef_col)); + else + /* (note: above we cut off the 'default' string at 128) */ + default_str = PQgetvalue(res, i, attrdef_col); - printTableAddCell(&cont, default_str, false, false); + printTableAddCell(&cont, default_str, false, generated[0] ? true : false); } /* Info for index columns */ diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h index 66d1b2fc40..a592d22a0e 100644 --- a/src/include/access/tupdesc.h +++ b/src/include/access/tupdesc.h @@ -42,6 +42,7 @@ typedef struct TupleConstr uint16 num_defval; uint16 num_check; bool has_not_null; + bool has_generated_stored; } TupleConstr; /* diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index 85076d0743..83ff373c30 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -28,6 +28,7 @@ typedef struct RawColumnDefault AttrNumber attnum; /* attribute to attach default to */ Node *raw_default; /* default value (untransformed parse tree) */ bool missingMode; /* true if part of add column processing */ + char generated; /* attgenerated setting */ } RawColumnDefault; typedef struct CookedConstraint @@ -120,7 +121,8 @@ extern Node *cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, int32 atttypmod, - const char *attname); + const char *attname, + char attgenerated); extern void DeleteRelationTuple(Oid relid); extern void DeleteAttributeTuples(Oid relid); diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index a6ec122389..04004b5703 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -140,6 +140,9 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(75, /* One of the ATTRIBUTE_IDENTITY_* constants below, or '\0' */ char attidentity BKI_DEFAULT('\0'); + /* One of the ATTRIBUTE_GENERATED_* constants below, or '\0' */ + char attgenerated BKI_DEFAULT('\0'); + /* Is dropped (ie, logically invisible) or not */ bool attisdropped BKI_DEFAULT(f); @@ -201,6 +204,8 @@ typedef FormData_pg_attribute *Form_pg_attribute; #define ATTRIBUTE_IDENTITY_ALWAYS 'a' #define ATTRIBUTE_IDENTITY_BY_DEFAULT 'd' +#define ATTRIBUTE_GENERATED_STORED 's' + #endif /* EXPOSE_TO_CLIENT_CODE */ #endif /* PG_ATTRIBUTE_H */ diff --git a/src/include/catalog/pg_class.dat b/src/include/catalog/pg_class.dat index c89710bc60..9bcf28676d 100644 --- a/src/include/catalog/pg_class.dat +++ b/src/include/catalog/pg_class.dat @@ -34,7 +34,7 @@ relname => 'pg_attribute', reltype => 'pg_attribute', relam => 'heap', relfilenode => '0', relpages => '0', reltuples => '0', relallvisible => '0', reltoastrelid => '0', relhasindex => 'f', relisshared => 'f', - relpersistence => 'p', relkind => 'r', relnatts => '24', relchecks => '0', + relpersistence => 'p', relkind => 'r', relnatts => '25', relchecks => '0', relhasrules => 'f', relhastriggers => 'f', relhassubclass => 'f', relrowsecurity => 'f', relforcerowsecurity => 'f', relispopulated => 't', relreplident => 'n', relispartition => 'f', relfrozenxid => '3', diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h index b8b289efc0..891b119608 100644 --- a/src/include/executor/nodeModifyTable.h +++ b/src/include/executor/nodeModifyTable.h @@ -15,6 +15,8 @@ #include "nodes/execnodes.h" +extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot); + extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags); extern void ExecEndModifyTable(ModifyTableState *node); extern void ExecReScanModifyTable(ModifyTableState *node); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 869c303e15..dbd7ed0363 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -452,6 +452,9 @@ typedef struct ResultRelInfo /* array of constraint-checking expr states */ ExprState **ri_ConstraintExprs; + /* array of stored generated columns expr states */ + ExprState **ri_GeneratedExprs; + /* for removing junk attributes from tuples */ JunkFilter *ri_junkFilter; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index bdd2bd2fd9..4469e922ba 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -655,6 +655,7 @@ typedef struct ColumnDef char identity; /* attidentity setting */ RangeVar *identitySequence; /* to store identity sequence name for * ALTER TABLE ... ADD COLUMN */ + char generated; /* attgenerated setting */ CollateClause *collClause; /* untransformed COLLATE spec, if any */ Oid collOid; /* collation OID (InvalidOid if not set) */ List *constraints; /* other constraints on column */ @@ -677,10 +678,11 @@ typedef enum TableLikeOption CREATE_TABLE_LIKE_COMMENTS = 1 << 0, CREATE_TABLE_LIKE_CONSTRAINTS = 1 << 1, CREATE_TABLE_LIKE_DEFAULTS = 1 << 2, - CREATE_TABLE_LIKE_IDENTITY = 1 << 3, - CREATE_TABLE_LIKE_INDEXES = 1 << 4, - CREATE_TABLE_LIKE_STATISTICS = 1 << 5, - CREATE_TABLE_LIKE_STORAGE = 1 << 6, + CREATE_TABLE_LIKE_GENERATED = 1 << 3, + CREATE_TABLE_LIKE_IDENTITY = 1 << 4, + CREATE_TABLE_LIKE_INDEXES = 1 << 5, + CREATE_TABLE_LIKE_STATISTICS = 1 << 6, + CREATE_TABLE_LIKE_STORAGE = 1 << 7, CREATE_TABLE_LIKE_ALL = PG_INT32_MAX } TableLikeOption; @@ -933,6 +935,15 @@ typedef struct PartitionCmd * them in these fields. A whole-row Var reference is represented by * setting the bit for InvalidAttrNumber. * + * updatedCols is also used in some other places, for example, to determine + * which triggers to fire and in FDWs to know which changed columns they + * need to ship off. Generated columns that are caused to be updated by an + * update to a base column are collected in extraUpdatedCols. This is not + * considered for permission checking, but it is useful in those places + * that want to know the full set of columns being updated as opposed to + * only the ones the user explicitly mentioned in the query. (There is + * currently no need for an extraInsertedCols, but it could exist.) + * * securityQuals is a list of security barrier quals (boolean expressions), * to be tested in the listed order before returning a row from the * relation. It is always NIL in parser output. Entries are added by the @@ -1087,6 +1098,7 @@ typedef struct RangeTblEntry Bitmapset *selectedCols; /* columns needing SELECT permission */ Bitmapset *insertedCols; /* columns needing INSERT permission */ Bitmapset *updatedCols; /* columns needing UPDATE permission */ + Bitmapset *extraUpdatedCols; /* generated columns being updated */ List *securityQuals; /* security barrier quals to apply, if any */ } RangeTblEntry; @@ -2086,6 +2098,7 @@ typedef enum ConstrType /* types of constraints */ CONSTR_NOTNULL, CONSTR_DEFAULT, CONSTR_IDENTITY, + CONSTR_GENERATED, CONSTR_CHECK, CONSTR_PRIMARY, CONSTR_UNIQUE, @@ -2124,7 +2137,8 @@ typedef struct Constraint bool is_no_inherit; /* is constraint non-inheritable? */ Node *raw_expr; /* expr, as untransformed parse tree */ char *cooked_expr; /* expr, as nodeToString representation */ - char generated_when; + char generated_when; /* ALWAYS or BY DEFAULT */ + char generated_kind; /* currently always STORED */ /* Fields used for unique constraints (UNIQUE and PRIMARY KEY): */ List *keys; /* String nodes naming referenced key diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h index c337f047cb..c556e0f258 100644 --- a/src/include/optimizer/plancat.h +++ b/src/include/optimizer/plancat.h @@ -71,4 +71,6 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node); extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event); +extern bool has_stored_generated_columns(PlannerInfo *root, Index rti); + #endif /* PLANCAT_H */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index f05444008c..00ace8425e 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -383,6 +383,7 @@ PG_KEYWORD("statistics", STATISTICS, UNRESERVED_KEYWORD) PG_KEYWORD("stdin", STDIN, UNRESERVED_KEYWORD) PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD) PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD) +PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD) PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD) PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD) PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD) diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index ea99a0954b..3d8039aa51 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -71,7 +71,8 @@ typedef enum ParseExprKind EXPR_KIND_PARTITION_BOUND, /* partition bound expression */ EXPR_KIND_PARTITION_EXPRESSION, /* PARTITION BY expression */ EXPR_KIND_CALL_ARGUMENT, /* procedure argument in CALL */ - EXPR_KIND_COPY_WHERE /* WHERE condition in COPY FROM */ + EXPR_KIND_COPY_WHERE, /* WHERE condition in COPY FROM */ + EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */ } ParseExprKind; diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index b9a9ecb7cc..9606d021b1 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -86,6 +86,7 @@ extern Oid get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype, int16 procnum); extern char *get_attname(Oid relid, AttrNumber attnum, bool missing_ok); extern AttrNumber get_attnum(Oid relid, const char *attname); +extern char get_attgenerated(Oid relid, AttrNumber attnum); extern Oid get_atttype(Oid relid, AttrNumber attnum); extern void get_atttypetypmodcoll(Oid relid, AttrNumber attnum, Oid *typid, int32 *typmod, Oid *collid); diff --git a/src/pl/plperl/expected/plperl_trigger.out b/src/pl/plperl/expected/plperl_trigger.out index 28011cd9f6..d4879e2f03 100644 --- a/src/pl/plperl/expected/plperl_trigger.out +++ b/src/pl/plperl/expected/plperl_trigger.out @@ -6,6 +6,10 @@ CREATE TABLE trigger_test ( v varchar, foo rowcompnest ); +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger LANGUAGE plperl AS $$ # make sure keys are sorted for consistent results - perl no longer @@ -98,6 +102,79 @@ NOTICE: $_TD->{table_name} = 'trigger_test' NOTICE: $_TD->{table_schema} = 'public' NOTICE: $_TD->{when} = 'BEFORE' DROP TRIGGER show_trigger_data_trig on trigger_test; +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +insert into trigger_test_generated (i) values (1); +NOTICE: $_TD->{argc} = '0' +NOTICE: $_TD->{event} = 'INSERT' +NOTICE: $_TD->{level} = 'ROW' +NOTICE: $_TD->{name} = 'show_trigger_data_trig_before' +NOTICE: $_TD->{new} = {'i' => '1'} +NOTICE: $_TD->{relid} = 'bogus:12345' +NOTICE: $_TD->{relname} = 'trigger_test_generated' +NOTICE: $_TD->{table_name} = 'trigger_test_generated' +NOTICE: $_TD->{table_schema} = 'public' +NOTICE: $_TD->{when} = 'BEFORE' +NOTICE: $_TD->{argc} = '0' +NOTICE: $_TD->{event} = 'INSERT' +NOTICE: $_TD->{level} = 'ROW' +NOTICE: $_TD->{name} = 'show_trigger_data_trig_after' +NOTICE: $_TD->{new} = {'i' => '1', 'j' => '2'} +NOTICE: $_TD->{relid} = 'bogus:12345' +NOTICE: $_TD->{relname} = 'trigger_test_generated' +NOTICE: $_TD->{table_name} = 'trigger_test_generated' +NOTICE: $_TD->{table_schema} = 'public' +NOTICE: $_TD->{when} = 'AFTER' +update trigger_test_generated set i = 11 where i = 1; +NOTICE: $_TD->{argc} = '0' +NOTICE: $_TD->{event} = 'UPDATE' +NOTICE: $_TD->{level} = 'ROW' +NOTICE: $_TD->{name} = 'show_trigger_data_trig_before' +NOTICE: $_TD->{new} = {'i' => '11'} +NOTICE: $_TD->{old} = {'i' => '1', 'j' => '2'} +NOTICE: $_TD->{relid} = 'bogus:12345' +NOTICE: $_TD->{relname} = 'trigger_test_generated' +NOTICE: $_TD->{table_name} = 'trigger_test_generated' +NOTICE: $_TD->{table_schema} = 'public' +NOTICE: $_TD->{when} = 'BEFORE' +NOTICE: $_TD->{argc} = '0' +NOTICE: $_TD->{event} = 'UPDATE' +NOTICE: $_TD->{level} = 'ROW' +NOTICE: $_TD->{name} = 'show_trigger_data_trig_after' +NOTICE: $_TD->{new} = {'i' => '11', 'j' => '22'} +NOTICE: $_TD->{old} = {'i' => '1', 'j' => '2'} +NOTICE: $_TD->{relid} = 'bogus:12345' +NOTICE: $_TD->{relname} = 'trigger_test_generated' +NOTICE: $_TD->{table_name} = 'trigger_test_generated' +NOTICE: $_TD->{table_schema} = 'public' +NOTICE: $_TD->{when} = 'AFTER' +delete from trigger_test_generated; +NOTICE: $_TD->{argc} = '0' +NOTICE: $_TD->{event} = 'DELETE' +NOTICE: $_TD->{level} = 'ROW' +NOTICE: $_TD->{name} = 'show_trigger_data_trig_before' +NOTICE: $_TD->{old} = {'i' => '11', 'j' => '22'} +NOTICE: $_TD->{relid} = 'bogus:12345' +NOTICE: $_TD->{relname} = 'trigger_test_generated' +NOTICE: $_TD->{table_name} = 'trigger_test_generated' +NOTICE: $_TD->{table_schema} = 'public' +NOTICE: $_TD->{when} = 'BEFORE' +NOTICE: $_TD->{argc} = '0' +NOTICE: $_TD->{event} = 'DELETE' +NOTICE: $_TD->{level} = 'ROW' +NOTICE: $_TD->{name} = 'show_trigger_data_trig_after' +NOTICE: $_TD->{old} = {'i' => '11', 'j' => '22'} +NOTICE: $_TD->{relid} = 'bogus:12345' +NOTICE: $_TD->{relname} = 'trigger_test_generated' +NOTICE: $_TD->{table_name} = 'trigger_test_generated' +NOTICE: $_TD->{table_schema} = 'public' +NOTICE: $_TD->{when} = 'AFTER' +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; insert into trigger_test values(1,'insert', '("(1)")'); CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; CREATE TRIGGER show_trigger_data_trig @@ -295,3 +372,21 @@ NOTICE: perlsnitch: ddl_command_start DROP TABLE NOTICE: perlsnitch: ddl_command_end DROP TABLE drop event trigger perl_a_snitch; drop event trigger perl_b_snitch; +-- dealing with generated columns +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE plperl +AS $$ +$_TD->{new}{j} = 5; # not allowed +return 'MODIFY'; +$$; +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +ERROR: cannot set generated column "j" +CONTEXT: PL/Perl function "generated_test_func1" +SELECT * FROM trigger_test_generated; + i | j +---+--- +(0 rows) + diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 35d5d121a0..31ba2f262f 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -266,7 +266,7 @@ static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger); -static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc); +static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc, bool include_generated); static SV *plperl_hash_from_datum(Datum attr); static SV *plperl_ref_from_pg_array(Datum arg, Oid typid); static SV *split_array(plperl_array_info *info, int first, int last, int nest); @@ -1644,13 +1644,19 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo) hv_store_string(hv, "name", cstr2sv(tdata->tg_trigger->tgname)); hv_store_string(hv, "relid", cstr2sv(relid)); + /* + * Note: In BEFORE trigger, stored generated columns are not computed yet, + * so don't make them accessible in NEW row. + */ + if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) { event = "INSERT"; if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) hv_store_string(hv, "new", plperl_hash_from_tuple(tdata->tg_trigtuple, - tupdesc)); + tupdesc, + !TRIGGER_FIRED_BEFORE(tdata->tg_event))); } else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) { @@ -1658,7 +1664,8 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo) if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) hv_store_string(hv, "old", plperl_hash_from_tuple(tdata->tg_trigtuple, - tupdesc)); + tupdesc, + true)); } else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) { @@ -1667,10 +1674,12 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo) { hv_store_string(hv, "old", plperl_hash_from_tuple(tdata->tg_trigtuple, - tupdesc)); + tupdesc, + true)); hv_store_string(hv, "new", plperl_hash_from_tuple(tdata->tg_newtuple, - tupdesc)); + tupdesc, + !TRIGGER_FIRED_BEFORE(tdata->tg_event))); } } else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event)) @@ -1791,6 +1800,11 @@ plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot set system attribute \"%s\"", key))); + if (attr->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("cannot set generated column \"%s\"", + key))); modvalues[attn - 1] = plperl_sv_to_datum(val, attr->atttypid, @@ -3012,7 +3026,7 @@ plperl_hash_from_datum(Datum attr) tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; - sv = plperl_hash_from_tuple(&tmptup, tupdesc); + sv = plperl_hash_from_tuple(&tmptup, tupdesc, true); ReleaseTupleDesc(tupdesc); return sv; @@ -3020,7 +3034,7 @@ plperl_hash_from_datum(Datum attr) /* Build a hash from all attributes of a given tuple. */ static SV * -plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc) +plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc, bool include_generated) { dTHX; HV *hv; @@ -3044,6 +3058,13 @@ plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc) if (att->attisdropped) continue; + if (att->attgenerated) + { + /* don't include unless requested */ + if (!include_generated) + continue; + } + attname = NameStr(att->attname); attr = heap_getattr(tuple, i + 1, tupdesc, &isnull); @@ -3198,7 +3219,7 @@ plperl_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 processed, av_extend(rows, processed); for (i = 0; i < processed; i++) { - row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc); + row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc, true); av_push(rows, row); } hv_store_string(result, "rows", @@ -3484,7 +3505,8 @@ plperl_spi_fetchrow(char *cursor) else { row = plperl_hash_from_tuple(SPI_tuptable->vals[0], - SPI_tuptable->tupdesc); + SPI_tuptable->tupdesc, + true); } SPI_freetuptable(SPI_tuptable); } diff --git a/src/pl/plperl/sql/plperl_trigger.sql b/src/pl/plperl/sql/plperl_trigger.sql index 624193b9d0..4adddeb80a 100644 --- a/src/pl/plperl/sql/plperl_trigger.sql +++ b/src/pl/plperl/sql/plperl_trigger.sql @@ -8,6 +8,11 @@ CREATE TABLE trigger_test ( foo rowcompnest ); +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); + CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger LANGUAGE plperl AS $$ # make sure keys are sorted for consistent results - perl no longer @@ -70,6 +75,21 @@ CREATE TRIGGER show_trigger_data_trig DROP TRIGGER show_trigger_data_trig on trigger_test; +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); + +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); + +insert into trigger_test_generated (i) values (1); +update trigger_test_generated set i = 11 where i = 1; +delete from trigger_test_generated; + +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; + insert into trigger_test values(1,'insert', '("(1)")'); CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; @@ -221,3 +241,19 @@ CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test drop event trigger perl_a_snitch; drop event trigger perl_b_snitch; + +-- dealing with generated columns + +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE plperl +AS $$ +$_TD->{new}{j} = 5; # not allowed +return 'MODIFY'; +$$; + +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); + +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +SELECT * FROM trigger_test_generated; diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 527cada4fe..f0005009b2 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -924,6 +924,26 @@ plpgsql_exec_trigger(PLpgSQL_function *func, false, false); expanded_record_set_tuple(rec_old->erh, trigdata->tg_trigtuple, false, false); + + /* + * In BEFORE trigger, stored generated columns are not computed yet, + * so make them null in the NEW row. (Only needed in UPDATE branch; + * in the INSERT case, they are already null, but in UPDATE, the field + * still contains the old value.) Alternatively, we could construct a + * whole new row structure without the generated columns, but this way + * seems more efficient and potentially less confusing. + */ + if (tupdesc->constr && tupdesc->constr->has_generated_stored && + TRIGGER_FIRED_BEFORE(trigdata->tg_event)) + { + for (int i = 0; i < tupdesc->natts; i++) + if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED) + expanded_record_set_field_internal(rec_new->erh, + i + 1, + (Datum) 0, + true, /*isnull*/ + false, false); + } } else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) { diff --git a/src/pl/plpython/expected/plpython_trigger.out b/src/pl/plpython/expected/plpython_trigger.out index d7ab8ac6b8..742988a5b5 100644 --- a/src/pl/plpython/expected/plpython_trigger.out +++ b/src/pl/plpython/expected/plpython_trigger.out @@ -67,6 +67,10 @@ SELECT * FROM users; -- dump trigger data CREATE TABLE trigger_test (i int, v text ); +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpythonu AS $$ if 'relid' in TD: @@ -203,6 +207,77 @@ NOTICE: TD[when] => BEFORE DROP TRIGGER show_trigger_data_trig_stmt on trigger_test; DROP TRIGGER show_trigger_data_trig_before on trigger_test; DROP TRIGGER show_trigger_data_trig_after on trigger_test; +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +insert into trigger_test_generated (i) values (1); +NOTICE: TD[args] => None +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => {'i': 1} +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => None +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => {'i': 1, 'j': 2} +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +update trigger_test_generated set i = 11 where i = 1; +NOTICE: TD[args] => None +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => {'i': 11} +NOTICE: TD[old] => {'i': 1, 'j': 2} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => None +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => {'i': 11, 'j': 22} +NOTICE: TD[old] => {'i': 1, 'j': 2} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +delete from trigger_test_generated; +NOTICE: TD[args] => None +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => None +NOTICE: TD[old] => {'i': 11, 'j': 22} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => None +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => None +NOTICE: TD[old] => {'i': 11, 'j': 22} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; insert into trigger_test values(1,'insert'); CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; CREATE TRIGGER show_trigger_data_trig @@ -524,3 +599,22 @@ INFO: old: 1 -> a INFO: new: 1 -> b DROP TABLE transition_table_test; DROP FUNCTION transition_table_test_f(); +-- dealing with generated columns +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE plpythonu +AS $$ +TD['new']['j'] = 5 # not allowed +return 'MODIFY' +$$; +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +ERROR: cannot set generated column "j" +CONTEXT: while modifying trigger row +PL/Python function "generated_test_func1" +SELECT * FROM trigger_test_generated; + i | j +---+--- +(0 rows) + diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c index 45ac25b2ae..e4d543a4d4 100644 --- a/src/pl/plpython/plpy_cursorobject.c +++ b/src/pl/plpython/plpy_cursorobject.c @@ -357,7 +357,7 @@ PLy_cursor_iternext(PyObject *self) exec_ctx->curr_proc); ret = PLy_input_from_tuple(&cursor->result, SPI_tuptable->vals[0], - SPI_tuptable->tupdesc); + SPI_tuptable->tupdesc, true); } SPI_freetuptable(SPI_tuptable); @@ -453,7 +453,8 @@ PLy_cursor_fetch(PyObject *self, PyObject *args) { PyObject *row = PLy_input_from_tuple(&cursor->result, SPI_tuptable->vals[i], - SPI_tuptable->tupdesc); + SPI_tuptable->tupdesc, + true); PyList_SetItem(ret->rows, i, row); } diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c index 2137186241..fd6cdc4ce5 100644 --- a/src/pl/plpython/plpy_exec.c +++ b/src/pl/plpython/plpy_exec.c @@ -13,6 +13,7 @@ #include "executor/spi.h" #include "funcapi.h" #include "utils/builtins.h" +#include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/typcache.h" @@ -751,6 +752,11 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r PyDict_SetItemString(pltdata, "level", pltlevel); Py_DECREF(pltlevel); + /* + * Note: In BEFORE trigger, stored generated columns are not computed yet, + * so don't make them accessible in NEW row. + */ + if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) { pltevent = PyString_FromString("INSERT"); @@ -758,7 +764,8 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r PyDict_SetItemString(pltdata, "old", Py_None); pytnew = PLy_input_from_tuple(&proc->result_in, tdata->tg_trigtuple, - rel_descr); + rel_descr, + !TRIGGER_FIRED_BEFORE(tdata->tg_event)); PyDict_SetItemString(pltdata, "new", pytnew); Py_DECREF(pytnew); *rv = tdata->tg_trigtuple; @@ -770,7 +777,8 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r PyDict_SetItemString(pltdata, "new", Py_None); pytold = PLy_input_from_tuple(&proc->result_in, tdata->tg_trigtuple, - rel_descr); + rel_descr, + true); PyDict_SetItemString(pltdata, "old", pytold); Py_DECREF(pytold); *rv = tdata->tg_trigtuple; @@ -781,12 +789,14 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *r pytnew = PLy_input_from_tuple(&proc->result_in, tdata->tg_newtuple, - rel_descr); + rel_descr, + !TRIGGER_FIRED_BEFORE(tdata->tg_event)); PyDict_SetItemString(pltdata, "new", pytnew); Py_DECREF(pytnew); pytold = PLy_input_from_tuple(&proc->result_in, tdata->tg_trigtuple, - rel_descr); + rel_descr, + true); PyDict_SetItemString(pltdata, "old", pytold); Py_DECREF(pytold); *rv = tdata->tg_newtuple; @@ -952,6 +962,11 @@ PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot set system attribute \"%s\"", plattstr))); + if (TupleDescAttr(tupdesc, attn - 1)->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("cannot set generated column \"%s\"", + plattstr))); plval = PyDict_GetItem(plntup, platt); if (plval == NULL) diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c index 41155fc81e..fb23a7b3a4 100644 --- a/src/pl/plpython/plpy_spi.c +++ b/src/pl/plpython/plpy_spi.c @@ -419,7 +419,8 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status) { PyObject *row = PLy_input_from_tuple(&ininfo, tuptable->vals[i], - tuptable->tupdesc); + tuptable->tupdesc, + true); PyList_SetItem(result->rows, i, row); } diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c index d6a6a849c3..6365e461e9 100644 --- a/src/pl/plpython/plpy_typeio.c +++ b/src/pl/plpython/plpy_typeio.c @@ -41,7 +41,7 @@ static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d); static PyObject *PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim, char **dataptr_p, bits8 **bitmap_p, int *bitmask_p); static PyObject *PLyDict_FromComposite(PLyDatumToOb *arg, Datum d); -static PyObject *PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc); +static PyObject *PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated); /* conversion from Python objects to Datums */ static Datum PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv, @@ -134,7 +134,7 @@ PLy_output_convert(PLyObToDatum *arg, PyObject *val, bool *isnull) * but in practice all callers have the right tupdesc available. */ PyObject * -PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc) +PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated) { PyObject *dict; PLyExecutionContext *exec_ctx = PLy_current_execution_context(); @@ -148,7 +148,7 @@ PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc) oldcontext = MemoryContextSwitchTo(scratch_context); - dict = PLyDict_FromTuple(arg, tuple, desc); + dict = PLyDict_FromTuple(arg, tuple, desc, include_generated); MemoryContextSwitchTo(oldcontext); @@ -804,7 +804,7 @@ PLyDict_FromComposite(PLyDatumToOb *arg, Datum d) tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; - dict = PLyDict_FromTuple(arg, &tmptup, tupdesc); + dict = PLyDict_FromTuple(arg, &tmptup, tupdesc, true); ReleaseTupleDesc(tupdesc); @@ -815,7 +815,7 @@ PLyDict_FromComposite(PLyDatumToOb *arg, Datum d) * Transform a tuple into a Python dict object. */ static PyObject * -PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc) +PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated) { PyObject *volatile dict; @@ -842,6 +842,13 @@ PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc) if (attr->attisdropped) continue; + if (attr->attgenerated) + { + /* don't include unless requested */ + if (!include_generated) + continue; + } + key = NameStr(attr->attname); vattr = heap_getattr(tuple, (i + 1), desc, &is_null); diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h index 82bdfae548..f210178238 100644 --- a/src/pl/plpython/plpy_typeio.h +++ b/src/pl/plpython/plpy_typeio.h @@ -151,7 +151,7 @@ extern Datum PLy_output_convert(PLyObToDatum *arg, PyObject *val, bool *isnull); extern PyObject *PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, - TupleDesc desc); + TupleDesc desc, bool include_generated); extern void PLy_input_setup_func(PLyDatumToOb *arg, MemoryContext arg_mcxt, Oid typeOid, int32 typmod, diff --git a/src/pl/plpython/sql/plpython_trigger.sql b/src/pl/plpython/sql/plpython_trigger.sql index 79c24b714b..19852dc585 100644 --- a/src/pl/plpython/sql/plpython_trigger.sql +++ b/src/pl/plpython/sql/plpython_trigger.sql @@ -67,6 +67,11 @@ CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW CREATE TABLE trigger_test (i int, v text ); +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); + CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpythonu AS $$ if 'relid' in TD: @@ -109,6 +114,21 @@ CREATE TRIGGER show_trigger_data_trig_stmt DROP TRIGGER show_trigger_data_trig_before on trigger_test; DROP TRIGGER show_trigger_data_trig_after on trigger_test; +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); + +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); + +insert into trigger_test_generated (i) values (1); +update trigger_test_generated set i = 11 where i = 1; +delete from trigger_test_generated; + +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; + insert into trigger_test values(1,'insert'); CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; @@ -430,3 +450,20 @@ CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test DROP TABLE transition_table_test; DROP FUNCTION transition_table_test_f(); + + +-- dealing with generated columns + +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE plpythonu +AS $$ +TD['new']['j'] = 5 # not allowed +return 'MODIFY' +$$; + +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); + +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +SELECT * FROM trigger_test_generated; diff --git a/src/pl/tcl/expected/pltcl_trigger.out b/src/pl/tcl/expected/pltcl_trigger.out index 2d5daedc11..008ea19509 100644 --- a/src/pl/tcl/expected/pltcl_trigger.out +++ b/src/pl/tcl/expected/pltcl_trigger.out @@ -61,6 +61,10 @@ CREATE TABLE trigger_test ( ); -- Make certain dropped attributes are handled correctly ALTER TABLE trigger_test DROP dropme; +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); CREATE VIEW trigger_test_view AS SELECT i, v FROM trigger_test; CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$ if {$TG_table_name eq "trigger_test" && $TG_level eq "ROW" && $TG_op ne "DELETE"} { @@ -112,6 +116,12 @@ FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); CREATE TRIGGER statement_trigger BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(42,'statement trigger'); +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); CREATE TRIGGER show_trigger_data_view_trig INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view'); @@ -631,6 +641,75 @@ NOTICE: TG_table_name: trigger_test NOTICE: TG_table_schema: public NOTICE: TG_when: BEFORE NOTICE: args: {23 skidoo} +insert into trigger_test_generated (i) values (1); +NOTICE: NEW: {i: 1} +NOTICE: OLD: {} +NOTICE: TG_level: ROW +NOTICE: TG_name: show_trigger_data_trig_before +NOTICE: TG_op: INSERT +NOTICE: TG_relatts: {{} i j} +NOTICE: TG_relid: bogus:12345 +NOTICE: TG_table_name: trigger_test_generated +NOTICE: TG_table_schema: public +NOTICE: TG_when: BEFORE +NOTICE: args: {} +NOTICE: NEW: {i: 1, j: 2} +NOTICE: OLD: {} +NOTICE: TG_level: ROW +NOTICE: TG_name: show_trigger_data_trig_after +NOTICE: TG_op: INSERT +NOTICE: TG_relatts: {{} i j} +NOTICE: TG_relid: bogus:12345 +NOTICE: TG_table_name: trigger_test_generated +NOTICE: TG_table_schema: public +NOTICE: TG_when: AFTER +NOTICE: args: {} +update trigger_test_generated set i = 11 where i = 1; +NOTICE: NEW: {i: 11} +NOTICE: OLD: {i: 1, j: 2} +NOTICE: TG_level: ROW +NOTICE: TG_name: show_trigger_data_trig_before +NOTICE: TG_op: UPDATE +NOTICE: TG_relatts: {{} i j} +NOTICE: TG_relid: bogus:12345 +NOTICE: TG_table_name: trigger_test_generated +NOTICE: TG_table_schema: public +NOTICE: TG_when: BEFORE +NOTICE: args: {} +NOTICE: NEW: {i: 11, j: 22} +NOTICE: OLD: {i: 1, j: 2} +NOTICE: TG_level: ROW +NOTICE: TG_name: show_trigger_data_trig_after +NOTICE: TG_op: UPDATE +NOTICE: TG_relatts: {{} i j} +NOTICE: TG_relid: bogus:12345 +NOTICE: TG_table_name: trigger_test_generated +NOTICE: TG_table_schema: public +NOTICE: TG_when: AFTER +NOTICE: args: {} +delete from trigger_test_generated; +NOTICE: NEW: {} +NOTICE: OLD: {i: 11, j: 22} +NOTICE: TG_level: ROW +NOTICE: TG_name: show_trigger_data_trig_before +NOTICE: TG_op: DELETE +NOTICE: TG_relatts: {{} i j} +NOTICE: TG_relid: bogus:12345 +NOTICE: TG_table_name: trigger_test_generated +NOTICE: TG_table_schema: public +NOTICE: TG_when: BEFORE +NOTICE: args: {} +NOTICE: NEW: {} +NOTICE: OLD: {i: 11, j: 22} +NOTICE: TG_level: ROW +NOTICE: TG_name: show_trigger_data_trig_after +NOTICE: TG_op: DELETE +NOTICE: TG_relatts: {{} i j} +NOTICE: TG_relid: bogus:12345 +NOTICE: TG_table_name: trigger_test_generated +NOTICE: TG_table_schema: public +NOTICE: TG_when: AFTER +NOTICE: args: {} insert into trigger_test_view values(2,'insert'); NOTICE: NEW: {i: 2, v: insert} NOTICE: OLD: {} @@ -738,6 +817,8 @@ NOTICE: TG_table_name: trigger_test NOTICE: TG_table_schema: public NOTICE: TG_when: BEFORE NOTICE: args: {42 {statement trigger}} +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; -- should error insert into trigger_test(test_argisnull) values(true); NOTICE: NEW: {} @@ -787,3 +868,21 @@ INFO: old: 1 -> a INFO: new: 1 -> b drop table transition_table_test; drop function transition_table_test_f(); +-- dealing with generated columns +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE pltcl +AS $$ +# not allowed +set NEW(j) 5 +return [array get NEW] +$$; +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +ERROR: cannot set generated column "j" +SELECT * FROM trigger_test_generated; + i | j +---+--- +(0 rows) + diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index 76c9afc339..1362ca51d1 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -324,7 +324,7 @@ static void pltcl_subtrans_abort(Tcl_Interp *interp, static void pltcl_set_tuple_values(Tcl_Interp *interp, const char *arrayname, uint64 tupno, HeapTuple tuple, TupleDesc tupdesc); -static Tcl_Obj *pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc); +static Tcl_Obj *pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc, bool include_generated); static HeapTuple pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc, pltcl_call_state *call_state); @@ -889,7 +889,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state, tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; - list_tmp = pltcl_build_tuple_argument(&tmptup, tupdesc); + list_tmp = pltcl_build_tuple_argument(&tmptup, tupdesc, true); Tcl_ListObjAppendElement(NULL, tcl_cmd, list_tmp); ReleaseTupleDesc(tupdesc); @@ -1060,7 +1060,6 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state, volatile HeapTuple rettup; Tcl_Obj *tcl_cmd; Tcl_Obj *tcl_trigtup; - Tcl_Obj *tcl_newtup; int tcl_rc; int i; const char *result; @@ -1162,20 +1161,22 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state, Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewStringObj("ROW", -1)); - /* Build the data list for the trigtuple */ - tcl_trigtup = pltcl_build_tuple_argument(trigdata->tg_trigtuple, - tupdesc); - /* * Now the command part of the event for TG_op and data for NEW * and OLD + * + * Note: In BEFORE trigger, stored generated columns are not computed yet, + * so don't make them accessible in NEW row. */ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) { Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewStringObj("INSERT", -1)); - Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup); + Tcl_ListObjAppendElement(NULL, tcl_cmd, + pltcl_build_tuple_argument(trigdata->tg_trigtuple, + tupdesc, + !TRIGGER_FIRED_BEFORE(trigdata->tg_event))); Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj()); rettup = trigdata->tg_trigtuple; @@ -1186,7 +1187,10 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state, Tcl_NewStringObj("DELETE", -1)); Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj()); - Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup); + Tcl_ListObjAppendElement(NULL, tcl_cmd, + pltcl_build_tuple_argument(trigdata->tg_trigtuple, + tupdesc, + true)); rettup = trigdata->tg_trigtuple; } @@ -1195,11 +1199,14 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state, Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewStringObj("UPDATE", -1)); - tcl_newtup = pltcl_build_tuple_argument(trigdata->tg_newtuple, - tupdesc); - - Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_newtup); - Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup); + Tcl_ListObjAppendElement(NULL, tcl_cmd, + pltcl_build_tuple_argument(trigdata->tg_newtuple, + tupdesc, + !TRIGGER_FIRED_BEFORE(trigdata->tg_event))); + Tcl_ListObjAppendElement(NULL, tcl_cmd, + pltcl_build_tuple_argument(trigdata->tg_trigtuple, + tupdesc, + true)); rettup = trigdata->tg_newtuple; } @@ -3091,7 +3098,7 @@ pltcl_set_tuple_values(Tcl_Interp *interp, const char *arrayname, * from all attributes of a given tuple **********************************************************************/ static Tcl_Obj * -pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc) +pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc, bool include_generated) { Tcl_Obj *retobj = Tcl_NewObj(); int i; @@ -3110,6 +3117,13 @@ pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc) if (att->attisdropped) continue; + if (att->attgenerated) + { + /* don't include unless requested */ + if (!include_generated) + continue; + } + /************************************************************ * Get the attribute name ************************************************************/ @@ -3219,6 +3233,12 @@ pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc, errmsg("cannot set system attribute \"%s\"", fieldName))); + if (TupleDescAttr(tupdesc, attn - 1)->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("cannot set generated column \"%s\"", + fieldName))); + values[attn - 1] = utf_u2e(Tcl_GetString(kvObjv[i + 1])); } diff --git a/src/pl/tcl/sql/pltcl_trigger.sql b/src/pl/tcl/sql/pltcl_trigger.sql index 277d9a0413..2db75a333a 100644 --- a/src/pl/tcl/sql/pltcl_trigger.sql +++ b/src/pl/tcl/sql/pltcl_trigger.sql @@ -71,6 +71,11 @@ CREATE TABLE trigger_test ( -- Make certain dropped attributes are handled correctly ALTER TABLE trigger_test DROP dropme; +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); + CREATE VIEW trigger_test_view AS SELECT i, v FROM trigger_test; CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$ @@ -125,6 +130,13 @@ CREATE TRIGGER statement_trigger BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(42,'statement trigger'); +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); + CREATE TRIGGER show_trigger_data_view_trig INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view'); @@ -531,6 +543,10 @@ CREATE TRIGGER show_trigger_data_view_trig -- show dump of trigger data insert into trigger_test values(1,'insert'); +insert into trigger_test_generated (i) values (1); +update trigger_test_generated set i = 11 where i = 1; +delete from trigger_test_generated; + insert into trigger_test_view values(2,'insert'); update trigger_test_view set v = 'update' where i=1; delete from trigger_test_view; @@ -540,6 +556,9 @@ CREATE TRIGGER show_trigger_data_view_trig delete from trigger_test; truncate trigger_test; +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; + -- should error insert into trigger_test(test_argisnull) values(true); @@ -565,3 +584,20 @@ CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test update transition_table_test set name = 'b'; drop table transition_table_test; drop function transition_table_test_f(); + +-- dealing with generated columns + +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE pltcl +AS $$ +# not allowed +set NEW(j) 5 +return [array get NEW] +$$; + +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); + +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +SELECT * FROM trigger_test_generated; diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out index b582211270..31db405175 100644 --- a/src/test/regress/expected/create_table_like.out +++ b/src/test/regress/expected/create_table_like.out @@ -113,6 +113,52 @@ SELECT * FROM test_like_id_3; -- identity was copied and applied (1 row) DROP TABLE test_like_id_1, test_like_id_2, test_like_id_3; +CREATE TABLE test_like_gen_1 (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +\d test_like_gen_1 + Table "public.test_like_gen_1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | | + b | integer | | | generated always as (a * 2) stored + +INSERT INTO test_like_gen_1 (a) VALUES (1); +SELECT * FROM test_like_gen_1; + a | b +---+--- + 1 | 2 +(1 row) + +CREATE TABLE test_like_gen_2 (LIKE test_like_gen_1); +\d test_like_gen_2 + Table "public.test_like_gen_2" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | + +INSERT INTO test_like_gen_2 (a) VALUES (1); +SELECT * FROM test_like_gen_2; + a | b +---+--- + 1 | +(1 row) + +CREATE TABLE test_like_gen_3 (LIKE test_like_gen_1 INCLUDING GENERATED); +\d test_like_gen_3 + Table "public.test_like_gen_3" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | | + b | integer | | | generated always as (a * 2) stored + +INSERT INTO test_like_gen_3 (a) VALUES (1); +SELECT * FROM test_like_gen_3; + a | b +---+--- + 1 | 2 +(1 row) + +DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3; CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */ INSERT INTO inhg VALUES (5, 10); INSERT INTO inhg VALUES (20, 10); -- should fail diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out new file mode 100644 index 0000000000..e0d1d88f17 --- /dev/null +++ b/src/test/regress/expected/generated.out @@ -0,0 +1,768 @@ +-- sanity check of system catalog +SELECT attrelid, attname, attgenerated FROM pg_attribute WHERE attgenerated NOT IN ('', 's'); + attrelid | attname | attgenerated +----------+---------+-------------- +(0 rows) + +CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55) STORED); +CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +SELECT table_name, column_name, column_default, is_nullable, is_generated, generation_expression FROM information_schema.columns WHERE table_name LIKE 'gtest_' ORDER BY 1, 2; + table_name | column_name | column_default | is_nullable | is_generated | generation_expression +------------+-------------+----------------+-------------+--------------+----------------------- + gtest0 | a | | NO | NEVER | + gtest0 | b | | YES | ALWAYS | 55 + gtest1 | a | | NO | NEVER | + gtest1 | b | | YES | ALWAYS | (a * 2) +(4 rows) + +SELECT table_name, column_name, dependent_column FROM information_schema.column_column_usage ORDER BY 1, 2, 3; + table_name | column_name | dependent_column +------------+-------------+------------------ + gtest1 | a | b +(1 row) + +\d gtest1 + Table "public.gtest1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | not null | + b | integer | | | generated always as (a * 2) stored +Indexes: + "gtest1_pkey" PRIMARY KEY, btree (a) + +-- duplicate generated +CREATE TABLE gtest_err_1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ALWAYS AS (a * 3) STORED); +ERROR: multiple generation clauses specified for column "b" of table "gtest_err_1" +LINE 1: ...ARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ... + ^ +-- references to other generated columns, including self-references +CREATE TABLE gtest_err_2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STORED); +ERROR: cannot use generated column "b" in column generation expression +LINE 1: ...2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STO... + ^ +DETAIL: A generated column cannot reference another generated column. +CREATE TABLE gtest_err_2b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STORED); +ERROR: cannot use generated column "b" in column generation expression +LINE 1: ...AYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STO... + ^ +DETAIL: A generated column cannot reference another generated column. +-- invalid reference +CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STORED); +ERROR: column "c" does not exist +LINE 1: ..._3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STO... + ^ +-- generation expression must be immutable +CREATE TABLE gtest_err_4 (a int PRIMARY KEY, b double precision GENERATED ALWAYS AS (random()) STORED); +ERROR: generation expression is not immutable +-- cannot have default/identity and generated +CREATE TABLE gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ALWAYS AS (a * 2) STORED); +ERROR: both default and generation expression specified for column "b" of table "gtest_err_5a" +LINE 1: ... gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ... + ^ +CREATE TABLE gtest_err_5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ALWAYS AS (a * 2) STORED); +ERROR: both identity and generation expression specified for column "b" of table "gtest_err_5b" +LINE 1: ...t PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ... + ^ +-- reference to system column not allowed in generated column +CREATE TABLE gtest_err_6a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37) STORED); +ERROR: cannot use system column "xmin" in column generation expression +LINE 1: ...a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37... + ^ +-- various prohibited constructs +CREATE TABLE gtest_err_7a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (avg(a)) STORED); +ERROR: aggregate functions are not allowed in column generation expressions +LINE 1: ...7a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (avg(a)) ST... + ^ +CREATE TABLE gtest_err_7b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (row_number() OVER (ORDER BY a)) STORED); +ERROR: window functions are not allowed in column generation expressions +LINE 1: ...7b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (row_number... + ^ +CREATE TABLE gtest_err_7c (a int PRIMARY KEY, b int GENERATED ALWAYS AS ((SELECT a)) STORED); +ERROR: cannot use subquery in column generation expression +LINE 1: ...7c (a int PRIMARY KEY, b int GENERATED ALWAYS AS ((SELECT a)... + ^ +CREATE TABLE gtest_err_7d (a int PRIMARY KEY, b int GENERATED ALWAYS AS (generate_series(1, a)) STORED); +ERROR: set-returning functions are not allowed in column generation expressions +LINE 1: ...7d (a int PRIMARY KEY, b int GENERATED ALWAYS AS (generate_s... + ^ +INSERT INTO gtest1 VALUES (1); +INSERT INTO gtest1 VALUES (2, DEFAULT); +INSERT INTO gtest1 VALUES (3, 33); -- error +ERROR: cannot insert into column "b" +DETAIL: Column "b" is a generated column. +SELECT * FROM gtest1 ORDER BY a; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +UPDATE gtest1 SET b = DEFAULT WHERE a = 1; +UPDATE gtest1 SET b = 11 WHERE a = 1; -- error +ERROR: column "b" can only be updated to DEFAULT +DETAIL: Column "b" is a generated column. +SELECT * FROM gtest1 ORDER BY a; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +SELECT a, b, b * 2 AS b2 FROM gtest1 ORDER BY a; + a | b | b2 +---+---+---- + 1 | 2 | 4 + 2 | 4 | 8 +(2 rows) + +SELECT a, b FROM gtest1 WHERE b = 4 ORDER BY a; + a | b +---+--- + 2 | 4 +(1 row) + +-- test that overflow error happens on write +INSERT INTO gtest1 VALUES (2000000000); +ERROR: integer out of range +SELECT * FROM gtest1; + a | b +---+--- + 2 | 4 + 1 | 2 +(2 rows) + +DELETE FROM gtest1 WHERE a = 2000000000; +-- test with joins +CREATE TABLE gtestx (x int, y int); +INSERT INTO gtestx VALUES (11, 1), (22, 2), (33, 3); +SELECT * FROM gtestx, gtest1 WHERE gtestx.y = gtest1.a; + x | y | a | b +----+---+---+--- + 11 | 1 | 1 | 2 + 22 | 2 | 2 | 4 +(2 rows) + +DROP TABLE gtestx; +-- test UPDATE/DELETE quals +SELECT * FROM gtest1 ORDER BY a; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +UPDATE gtest1 SET a = 3 WHERE b = 4; +SELECT * FROM gtest1 ORDER BY a; + a | b +---+--- + 1 | 2 + 3 | 6 +(2 rows) + +DELETE FROM gtest1 WHERE b = 2; +SELECT * FROM gtest1 ORDER BY a; + a | b +---+--- + 3 | 6 +(1 row) + +-- views +CREATE VIEW gtest1v AS SELECT * FROM gtest1; +SELECT * FROM gtest1v; + a | b +---+--- + 3 | 6 +(1 row) + +INSERT INTO gtest1v VALUES (4, 8); -- fails +ERROR: cannot insert into column "b" +DETAIL: Column "b" is a generated column. +DROP VIEW gtest1v; +-- CTEs +WITH foo AS (SELECT * FROM gtest1) SELECT * FROM foo; + a | b +---+--- + 3 | 6 +(1 row) + +-- inheritance +CREATE TABLE gtest1_1 () INHERITS (gtest1); +SELECT * FROM gtest1_1; + a | b +---+--- +(0 rows) + +\d gtest1_1 + Table "public.gtest1_1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | not null | + b | integer | | | generated always as (a * 2) stored +Inherits: gtest1 + +INSERT INTO gtest1_1 VALUES (4); +SELECT * FROM gtest1_1; + a | b +---+--- + 4 | 8 +(1 row) + +SELECT * FROM gtest1; + a | b +---+--- + 3 | 6 + 4 | 8 +(2 rows) + +-- test inheritance mismatch +CREATE TABLE gtesty (x int, b int); +CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty); -- error +NOTICE: merging multiple inherited definitions of column "b" +ERROR: inherited column "b" has a generation conflict +DROP TABLE gtesty; +-- test stored update +CREATE TABLE gtest3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 3) STORED); +INSERT INTO gtest3 (a) VALUES (1), (2), (3); +SELECT * FROM gtest3 ORDER BY a; + a | b +---+--- + 1 | 3 + 2 | 6 + 3 | 9 +(3 rows) + +UPDATE gtest3 SET a = 22 WHERE a = 2; +SELECT * FROM gtest3 ORDER BY a; + a | b +----+---- + 1 | 3 + 3 | 9 + 22 | 66 +(3 rows) + +-- COPY +TRUNCATE gtest1; +INSERT INTO gtest1 (a) VALUES (1), (2); +COPY gtest1 TO stdout; +1 +2 +COPY gtest1 (a, b) TO stdout; +ERROR: column "b" is a generated column +DETAIL: Generated columns cannot be used in COPY. +COPY gtest1 FROM stdin; +COPY gtest1 (a, b) FROM stdin; +ERROR: column "b" is a generated column +DETAIL: Generated columns cannot be used in COPY. +SELECT * FROM gtest1 ORDER BY a; + a | b +---+--- + 1 | 2 + 2 | 4 + 3 | 6 + 4 | 8 +(4 rows) + +TRUNCATE gtest3; +INSERT INTO gtest3 (a) VALUES (1), (2); +COPY gtest3 TO stdout; +1 +2 +COPY gtest3 (a, b) TO stdout; +ERROR: column "b" is a generated column +DETAIL: Generated columns cannot be used in COPY. +COPY gtest3 FROM stdin; +COPY gtest3 (a, b) FROM stdin; +ERROR: column "b" is a generated column +DETAIL: Generated columns cannot be used in COPY. +SELECT * FROM gtest3 ORDER BY a; + a | b +---+---- + 1 | 3 + 2 | 6 + 3 | 9 + 4 | 12 +(4 rows) + +-- null values +CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) STORED); +INSERT INTO gtest2 VALUES (1); +SELECT * FROM gtest2; + a | b +---+--- + 1 | +(1 row) + +-- composite types +CREATE TYPE double_int as (a int, b int); +CREATE TABLE gtest4 ( + a int, + b double_int GENERATED ALWAYS AS ((a * 2, a * 3)) STORED +); +INSERT INTO gtest4 VALUES (1), (6); +SELECT * FROM gtest4; + a | b +---+--------- + 1 | (2,3) + 6 | (12,18) +(2 rows) + +DROP TABLE gtest4; +DROP TYPE double_int; +-- using tableoid is allowed +CREATE TABLE gtest_tableoid ( + a int PRIMARY KEY, + b bool GENERATED ALWAYS AS (tableoid <> 0) STORED +); +INSERT INTO gtest_tableoid VALUES (1), (2); +SELECT * FROM gtest_tableoid; + a | b +---+--- + 1 | t + 2 | t +(2 rows) + +-- drop column behavior +CREATE TABLE gtest10 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED); +ALTER TABLE gtest10 DROP COLUMN b; +\d gtest10 + Table "public.gtest10" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | not null | +Indexes: + "gtest10_pkey" PRIMARY KEY, btree (a) + +CREATE TABLE gtest10a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +ALTER TABLE gtest10a DROP COLUMN b; +INSERT INTO gtest10a (a) VALUES (1); +-- privileges +CREATE USER regress_user11; +CREATE TABLE gtest11s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED); +INSERT INTO gtest11s VALUES (1, 10), (2, 20); +GRANT SELECT (a, c) ON gtest11s TO regress_user11; +CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL; +REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC; +CREATE TABLE gtest12s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) STORED); +INSERT INTO gtest12s VALUES (1, 10), (2, 20); +GRANT SELECT (a, c) ON gtest12s TO regress_user11; +SET ROLE regress_user11; +SELECT a, b FROM gtest11s; -- not allowed +ERROR: permission denied for table gtest11s +SELECT a, c FROM gtest11s; -- allowed + a | c +---+---- + 1 | 20 + 2 | 40 +(2 rows) + +SELECT gf1(10); -- not allowed +ERROR: permission denied for function gf1 +SELECT a, c FROM gtest12s; -- allowed + a | c +---+---- + 1 | 30 + 2 | 60 +(2 rows) + +RESET ROLE; +DROP TABLE gtest11s, gtest12s; +DROP FUNCTION gf1(int); +DROP USER regress_user11; +-- check constraints +CREATE TABLE gtest20 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED CHECK (b < 50)); +INSERT INTO gtest20 (a) VALUES (10); -- ok +INSERT INTO gtest20 (a) VALUES (30); -- violates constraint +ERROR: new row for relation "gtest20" violates check constraint "gtest20_b_check" +DETAIL: Failing row contains (30, 60). +CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest20a (a) VALUES (10); +INSERT INTO gtest20a (a) VALUES (30); +ALTER TABLE gtest20a ADD CHECK (b < 50); -- fails on existing row +ERROR: check constraint "gtest20a_b_check" is violated by some row +CREATE TABLE gtest20b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest20b (a) VALUES (10); +INSERT INTO gtest20b (a) VALUES (30); +ALTER TABLE gtest20b ADD CONSTRAINT chk CHECK (b < 50) NOT VALID; +ALTER TABLE gtest20b VALIDATE CONSTRAINT chk; -- fails on existing row +ERROR: check constraint "chk" is violated by some row +-- not-null constraints +CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED NOT NULL); +INSERT INTO gtest21a (a) VALUES (1); -- ok +INSERT INTO gtest21a (a) VALUES (0); -- violates constraint +ERROR: null value in column "b" violates not-null constraint +DETAIL: Failing row contains (0, null). +CREATE TABLE gtest21b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED); +ALTER TABLE gtest21b ALTER COLUMN b SET NOT NULL; +INSERT INTO gtest21b (a) VALUES (1); -- ok +INSERT INTO gtest21b (a) VALUES (0); -- violates constraint +ERROR: null value in column "b" violates not-null constraint +DETAIL: Failing row contains (0, null). +ALTER TABLE gtest21b ALTER COLUMN b DROP NOT NULL; +INSERT INTO gtest21b (a) VALUES (0); -- ok now +-- index constraints +CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) STORED UNIQUE); +INSERT INTO gtest22a VALUES (2); +INSERT INTO gtest22a VALUES (3); +ERROR: duplicate key value violates unique constraint "gtest22a_b_key" +DETAIL: Key (b)=(1) already exists. +INSERT INTO gtest22a VALUES (4); +CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) STORED, PRIMARY KEY (a, b)); +INSERT INTO gtest22b VALUES (2); +INSERT INTO gtest22b VALUES (2); +ERROR: duplicate key value violates unique constraint "gtest22b_pkey" +DETAIL: Key (a, b)=(2, 1) already exists. +-- indexes +CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +CREATE INDEX gtest22c_b_idx ON gtest22c (b); +CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); +CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; +\d gtest22c + Table "public.gtest22c" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | | + b | integer | | | generated always as (a * 2) stored +Indexes: + "gtest22c_b_idx" btree (b) + "gtest22c_expr_idx" btree ((b * 3)) + "gtest22c_pred_idx" btree (a) WHERE b > 0 + +INSERT INTO gtest22c VALUES (1), (2), (3); +SET enable_seqscan TO off; +SET enable_bitmapscan TO off; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4; + QUERY PLAN +--------------------------------------------- + Index Scan using gtest22c_b_idx on gtest22c + Index Cond: (b = 4) +(2 rows) + +SELECT * FROM gtest22c WHERE b = 4; + a | b +---+--- + 2 | 4 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6; + QUERY PLAN +------------------------------------------------ + Index Scan using gtest22c_expr_idx on gtest22c + Index Cond: ((b * 3) = 6) +(2 rows) + +SELECT * FROM gtest22c WHERE b * 3 = 6; + a | b +---+--- + 1 | 2 +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; + QUERY PLAN +------------------------------------------------ + Index Scan using gtest22c_pred_idx on gtest22c + Index Cond: (a = 1) +(2 rows) + +SELECT * FROM gtest22c WHERE a = 1 AND b > 0; + a | b +---+--- + 1 | 2 +(1 row) + +RESET enable_seqscan; +RESET enable_bitmapscan; +-- foreign keys +CREATE TABLE gtest23a (x int PRIMARY KEY, y int); +INSERT INTO gtest23a VALUES (1, 11), (2, 22), (3, 33); +CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON UPDATE CASCADE); -- error +ERROR: invalid ON UPDATE action for foreign key constraint containing generated column +CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON DELETE SET NULL); -- error +ERROR: invalid ON DELETE action for foreign key constraint containing generated column +CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x)); +\d gtest23b + Table "public.gtest23b" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | not null | + b | integer | | | generated always as (a * 2) stored +Indexes: + "gtest23b_pkey" PRIMARY KEY, btree (a) +Foreign-key constraints: + "gtest23b_b_fkey" FOREIGN KEY (b) REFERENCES gtest23a(x) + +INSERT INTO gtest23b VALUES (1); -- ok +INSERT INTO gtest23b VALUES (5); -- error +ERROR: insert or update on table "gtest23b" violates foreign key constraint "gtest23b_b_fkey" +DETAIL: Key (b)=(10) is not present in table "gtest23a". +DROP TABLE gtest23b; +DROP TABLE gtest23a; +CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) STORED, PRIMARY KEY (y)); +INSERT INTO gtest23p VALUES (1), (2), (3); +CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y)); +INSERT INTO gtest23q VALUES (1, 2); -- ok +INSERT INTO gtest23q VALUES (2, 5); -- error +ERROR: insert or update on table "gtest23q" violates foreign key constraint "gtest23q_b_fkey" +DETAIL: Key (b)=(5) is not present in table "gtest23p". +-- domains +CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10); +CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest24 (a) VALUES (4); -- ok +INSERT INTO gtest24 (a) VALUES (6); -- error +ERROR: value for domain gtestdomain1 violates check constraint "gtestdomain1_check" +-- typed tables (currently not supported) +CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint); +CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) STORED); +ERROR: generated columns are not supported on typed tables +DROP TYPE gtest_type CASCADE; +-- table partitions (currently not supported) +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); +CREATE TABLE gtest_child PARTITION OF gtest_parent ( + f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 2) STORED +) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error +ERROR: generated columns are not supported on partitions +DROP TABLE gtest_parent; +-- partitioned table +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f1); +CREATE TABLE gtest_child PARTITION OF gtest_parent FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1); +SELECT * FROM gtest_parent; + f1 | f2 | f3 +------------+----+---- + 07-15-2016 | 1 | 2 +(1 row) + +SELECT * FROM gtest_child; + f1 | f2 | f3 +------------+----+---- + 07-15-2016 | 1 | 2 +(1 row) + +DROP TABLE gtest_parent; +-- generated columns in partition key (not allowed) +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); +ERROR: cannot use generated column in partition key +LINE 1: ...ENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); + ^ +DETAIL: Column "f3" is a generated column. +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); +ERROR: cannot use generated column in partition key +LINE 1: ...ED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); + ^ +DETAIL: Column "f3" is a generated column. +-- ALTER TABLE ... ADD COLUMN +CREATE TABLE gtest25 (a int PRIMARY KEY); +INSERT INTO gtest25 VALUES (3), (4); +ALTER TABLE gtest25 ADD COLUMN b int GENERATED ALWAYS AS (a * 3) STORED; +SELECT * FROM gtest25 ORDER BY a; + a | b +---+---- + 3 | 9 + 4 | 12 +(2 rows) + +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4) STORED; -- error +ERROR: cannot use generated column "b" in column generation expression +DETAIL: A generated column cannot reference another generated column. +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error +ERROR: column "z" does not exist +-- ALTER TABLE ... ALTER COLUMN +CREATE TABLE gtest27 ( + a int, + b int GENERATED ALWAYS AS (a * 2) STORED +); +INSERT INTO gtest27 (a) VALUES (3), (4); +ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error +ERROR: cannot alter type of a column used by a generated column +DETAIL: Column "a" is used by generated column "b". +ALTER TABLE gtest27 ALTER COLUMN b TYPE numeric; +\d gtest27 + Table "public.gtest27" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+-------------------------------------- + a | integer | | | + b | numeric | | | generated always as ((a * 2)) stored + +SELECT * FROM gtest27; + a | b +---+--- + 3 | 6 + 4 | 8 +(2 rows) + +ALTER TABLE gtest27 ALTER COLUMN b TYPE boolean USING b <> 0; -- error +ERROR: generation expression for column "b" cannot be cast automatically to type boolean +ALTER TABLE gtest27 ALTER COLUMN b DROP DEFAULT; -- error +ERROR: column "b" of relation "gtest27" is a generated column +\d gtest27 + Table "public.gtest27" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+-------------------------------------- + a | integer | | | + b | numeric | | | generated always as ((a * 2)) stored + +-- triggers +CREATE TABLE gtest26 ( + a int PRIMARY KEY, + b int GENERATED ALWAYS AS (a * 2) STORED +); +CREATE FUNCTION gtest_trigger_func() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + IF tg_op IN ('DELETE', 'UPDATE') THEN + RAISE INFO '%: %: old = %', TG_NAME, TG_WHEN, OLD; + END IF; + IF tg_op IN ('INSERT', 'UPDATE') THEN + RAISE INFO '%: %: new = %', TG_NAME, TG_WHEN, NEW; + END IF; + IF tg_op = 'DELETE' THEN + RETURN OLD; + ELSE + RETURN NEW; + END IF; +END +$$; +CREATE TRIGGER gtest1 BEFORE DELETE OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (OLD.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); +CREATE TRIGGER gtest2a BEFORE INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.b < 0) -- error + EXECUTE PROCEDURE gtest_trigger_func(); +ERROR: BEFORE trigger's WHEN condition cannot reference NEW generated columns +LINE 3: WHEN (NEW.b < 0) + ^ +DETAIL: Column "b" is a generated column. +CREATE TRIGGER gtest2b BEFORE INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.* IS NOT NULL) -- error + EXECUTE PROCEDURE gtest_trigger_func(); +ERROR: BEFORE trigger's WHEN condition cannot reference NEW generated columns +LINE 3: WHEN (NEW.* IS NOT NULL) + ^ +DETAIL: A whole-row reference is used and the table contains generated columns. +CREATE TRIGGER gtest2 BEFORE INSERT ON gtest26 + FOR EACH ROW + WHEN (NEW.a < 0) + EXECUTE PROCEDURE gtest_trigger_func(); +CREATE TRIGGER gtest3 AFTER DELETE OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (OLD.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); +CREATE TRIGGER gtest4 AFTER INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); +INSERT INTO gtest26 (a) VALUES (-2), (0), (3); +INFO: gtest2: BEFORE: new = (-2,) +INFO: gtest4: AFTER: new = (-2,-4) +SELECT * FROM gtest26 ORDER BY a; + a | b +----+---- + -2 | -4 + 0 | 0 + 3 | 6 +(3 rows) + +UPDATE gtest26 SET a = a * -2; +INFO: gtest1: BEFORE: old = (-2,-4) +INFO: gtest1: BEFORE: new = (4,) +INFO: gtest3: AFTER: old = (-2,-4) +INFO: gtest3: AFTER: new = (4,8) +INFO: gtest4: AFTER: old = (3,6) +INFO: gtest4: AFTER: new = (-6,-12) +SELECT * FROM gtest26 ORDER BY a; + a | b +----+----- + -6 | -12 + 0 | 0 + 4 | 8 +(3 rows) + +DELETE FROM gtest26 WHERE a = -6; +INFO: gtest1: BEFORE: old = (-6,-12) +INFO: gtest3: AFTER: old = (-6,-12) +SELECT * FROM gtest26 ORDER BY a; + a | b +---+--- + 0 | 0 + 4 | 8 +(2 rows) + +DROP TRIGGER gtest1 ON gtest26; +DROP TRIGGER gtest2 ON gtest26; +DROP TRIGGER gtest3 ON gtest26; +-- Check that an UPDATE of "a" fires the trigger for UPDATE OF b, per +-- SQL standard. +CREATE FUNCTION gtest_trigger_func3() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + RAISE NOTICE 'OK'; + RETURN NEW; +END +$$; +CREATE TRIGGER gtest11 BEFORE UPDATE OF b ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func3(); +UPDATE gtest26 SET a = 1 WHERE a = 0; +NOTICE: OK +DROP TRIGGER gtest11 ON gtest26; +TRUNCATE gtest26; +-- check that modifications of stored generated columns in triggers do +-- not get propagated +CREATE FUNCTION gtest_trigger_func4() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + NEW.a = 10; + NEW.b = 300; + RETURN NEW; +END; +$$; +CREATE TRIGGER gtest12_01 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func(); +CREATE TRIGGER gtest12_02 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func4(); +CREATE TRIGGER gtest12_03 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func(); +INSERT INTO gtest26 (a) VALUES (1); +UPDATE gtest26 SET a = 11 WHERE a = 1; +INFO: gtest12_01: BEFORE: old = (1,2) +INFO: gtest12_01: BEFORE: new = (11,) +INFO: gtest12_03: BEFORE: old = (1,2) +INFO: gtest12_03: BEFORE: new = (10,) +SELECT * FROM gtest26 ORDER BY a; + a | b +----+---- + 10 | 20 +(1 row) + +-- LIKE INCLUDING GENERATED and dropped column handling +CREATE TABLE gtest28a ( + a int, + b int, + c int, + x int GENERATED ALWAYS AS (b * 2) STORED +); +ALTER TABLE gtest28a DROP COLUMN a; +CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED); +\d gtest28* + Table "public.gtest28a" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + b | integer | | | + c | integer | | | + x | integer | | | generated always as (b * 2) stored + + Table "public.gtest28b" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + b | integer | | | + c | integer | | | + x | integer | | | generated always as (b * 2) stored + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 908fbf650a..f320fb6ef3 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi # ---------- # Another group of parallel tests # ---------- -test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity +test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password identity generated # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index fa754d1c6b..36644aa963 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -122,6 +122,7 @@ test: groupingsets test: drop_operator test: password test: identity +test: generated test: create_table_like test: alter_generic test: alter_operator diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql index 65c3880792..9b19c680b5 100644 --- a/src/test/regress/sql/create_table_like.sql +++ b/src/test/regress/sql/create_table_like.sql @@ -51,6 +51,20 @@ CREATE TABLE test_like_id_3 (LIKE test_like_id_1 INCLUDING IDENTITY); SELECT * FROM test_like_id_3; -- identity was copied and applied DROP TABLE test_like_id_1, test_like_id_2, test_like_id_3; +CREATE TABLE test_like_gen_1 (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +\d test_like_gen_1 +INSERT INTO test_like_gen_1 (a) VALUES (1); +SELECT * FROM test_like_gen_1; +CREATE TABLE test_like_gen_2 (LIKE test_like_gen_1); +\d test_like_gen_2 +INSERT INTO test_like_gen_2 (a) VALUES (1); +SELECT * FROM test_like_gen_2; +CREATE TABLE test_like_gen_3 (LIKE test_like_gen_1 INCLUDING GENERATED); +\d test_like_gen_3 +INSERT INTO test_like_gen_3 (a) VALUES (1); +SELECT * FROM test_like_gen_3; +DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3; + CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */ INSERT INTO inhg VALUES (5, 10); INSERT INTO inhg VALUES (20, 10); -- should fail diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql new file mode 100644 index 0000000000..6d71594dc4 --- /dev/null +++ b/src/test/regress/sql/generated.sql @@ -0,0 +1,451 @@ +-- sanity check of system catalog +SELECT attrelid, attname, attgenerated FROM pg_attribute WHERE attgenerated NOT IN ('', 's'); + + +CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55) STORED); +CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); + +SELECT table_name, column_name, column_default, is_nullable, is_generated, generation_expression FROM information_schema.columns WHERE table_name LIKE 'gtest_' ORDER BY 1, 2; + +SELECT table_name, column_name, dependent_column FROM information_schema.column_column_usage ORDER BY 1, 2, 3; + +\d gtest1 + +-- duplicate generated +CREATE TABLE gtest_err_1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ALWAYS AS (a * 3) STORED); + +-- references to other generated columns, including self-references +CREATE TABLE gtest_err_2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STORED); +CREATE TABLE gtest_err_2b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STORED); + +-- invalid reference +CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STORED); + +-- generation expression must be immutable +CREATE TABLE gtest_err_4 (a int PRIMARY KEY, b double precision GENERATED ALWAYS AS (random()) STORED); + +-- cannot have default/identity and generated +CREATE TABLE gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ALWAYS AS (a * 2) STORED); +CREATE TABLE gtest_err_5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ALWAYS AS (a * 2) STORED); + +-- reference to system column not allowed in generated column +CREATE TABLE gtest_err_6a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37) STORED); + +-- various prohibited constructs +CREATE TABLE gtest_err_7a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (avg(a)) STORED); +CREATE TABLE gtest_err_7b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (row_number() OVER (ORDER BY a)) STORED); +CREATE TABLE gtest_err_7c (a int PRIMARY KEY, b int GENERATED ALWAYS AS ((SELECT a)) STORED); +CREATE TABLE gtest_err_7d (a int PRIMARY KEY, b int GENERATED ALWAYS AS (generate_series(1, a)) STORED); + +INSERT INTO gtest1 VALUES (1); +INSERT INTO gtest1 VALUES (2, DEFAULT); +INSERT INTO gtest1 VALUES (3, 33); -- error + +SELECT * FROM gtest1 ORDER BY a; + +UPDATE gtest1 SET b = DEFAULT WHERE a = 1; +UPDATE gtest1 SET b = 11 WHERE a = 1; -- error + +SELECT * FROM gtest1 ORDER BY a; + +SELECT a, b, b * 2 AS b2 FROM gtest1 ORDER BY a; +SELECT a, b FROM gtest1 WHERE b = 4 ORDER BY a; + +-- test that overflow error happens on write +INSERT INTO gtest1 VALUES (2000000000); +SELECT * FROM gtest1; +DELETE FROM gtest1 WHERE a = 2000000000; + +-- test with joins +CREATE TABLE gtestx (x int, y int); +INSERT INTO gtestx VALUES (11, 1), (22, 2), (33, 3); +SELECT * FROM gtestx, gtest1 WHERE gtestx.y = gtest1.a; +DROP TABLE gtestx; + +-- test UPDATE/DELETE quals +SELECT * FROM gtest1 ORDER BY a; +UPDATE gtest1 SET a = 3 WHERE b = 4; +SELECT * FROM gtest1 ORDER BY a; +DELETE FROM gtest1 WHERE b = 2; +SELECT * FROM gtest1 ORDER BY a; + +-- views +CREATE VIEW gtest1v AS SELECT * FROM gtest1; +SELECT * FROM gtest1v; +INSERT INTO gtest1v VALUES (4, 8); -- fails +DROP VIEW gtest1v; + +-- CTEs +WITH foo AS (SELECT * FROM gtest1) SELECT * FROM foo; + +-- inheritance +CREATE TABLE gtest1_1 () INHERITS (gtest1); +SELECT * FROM gtest1_1; +\d gtest1_1 +INSERT INTO gtest1_1 VALUES (4); +SELECT * FROM gtest1_1; +SELECT * FROM gtest1; + +-- test inheritance mismatch +CREATE TABLE gtesty (x int, b int); +CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty); -- error +DROP TABLE gtesty; + +-- test stored update +CREATE TABLE gtest3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 3) STORED); +INSERT INTO gtest3 (a) VALUES (1), (2), (3); +SELECT * FROM gtest3 ORDER BY a; +UPDATE gtest3 SET a = 22 WHERE a = 2; +SELECT * FROM gtest3 ORDER BY a; + +-- COPY +TRUNCATE gtest1; +INSERT INTO gtest1 (a) VALUES (1), (2); + +COPY gtest1 TO stdout; + +COPY gtest1 (a, b) TO stdout; + +COPY gtest1 FROM stdin; +3 +4 +\. + +COPY gtest1 (a, b) FROM stdin; + +SELECT * FROM gtest1 ORDER BY a; + +TRUNCATE gtest3; +INSERT INTO gtest3 (a) VALUES (1), (2); + +COPY gtest3 TO stdout; + +COPY gtest3 (a, b) TO stdout; + +COPY gtest3 FROM stdin; +3 +4 +\. + +COPY gtest3 (a, b) FROM stdin; + +SELECT * FROM gtest3 ORDER BY a; + +-- null values +CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) STORED); +INSERT INTO gtest2 VALUES (1); +SELECT * FROM gtest2; + +-- composite types +CREATE TYPE double_int as (a int, b int); +CREATE TABLE gtest4 ( + a int, + b double_int GENERATED ALWAYS AS ((a * 2, a * 3)) STORED +); +INSERT INTO gtest4 VALUES (1), (6); +SELECT * FROM gtest4; + +DROP TABLE gtest4; +DROP TYPE double_int; + +-- using tableoid is allowed +CREATE TABLE gtest_tableoid ( + a int PRIMARY KEY, + b bool GENERATED ALWAYS AS (tableoid <> 0) STORED +); +INSERT INTO gtest_tableoid VALUES (1), (2); +SELECT * FROM gtest_tableoid; + +-- drop column behavior +CREATE TABLE gtest10 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED); +ALTER TABLE gtest10 DROP COLUMN b; + +\d gtest10 + +CREATE TABLE gtest10a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +ALTER TABLE gtest10a DROP COLUMN b; +INSERT INTO gtest10a (a) VALUES (1); + +-- privileges +CREATE USER regress_user11; + +CREATE TABLE gtest11s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED); +INSERT INTO gtest11s VALUES (1, 10), (2, 20); +GRANT SELECT (a, c) ON gtest11s TO regress_user11; + +CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL; +REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC; + +CREATE TABLE gtest12s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) STORED); +INSERT INTO gtest12s VALUES (1, 10), (2, 20); +GRANT SELECT (a, c) ON gtest12s TO regress_user11; + +SET ROLE regress_user11; +SELECT a, b FROM gtest11s; -- not allowed +SELECT a, c FROM gtest11s; -- allowed +SELECT gf1(10); -- not allowed +SELECT a, c FROM gtest12s; -- allowed +RESET ROLE; + +DROP TABLE gtest11s, gtest12s; +DROP FUNCTION gf1(int); +DROP USER regress_user11; + +-- check constraints +CREATE TABLE gtest20 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED CHECK (b < 50)); +INSERT INTO gtest20 (a) VALUES (10); -- ok +INSERT INTO gtest20 (a) VALUES (30); -- violates constraint + +CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest20a (a) VALUES (10); +INSERT INTO gtest20a (a) VALUES (30); +ALTER TABLE gtest20a ADD CHECK (b < 50); -- fails on existing row + +CREATE TABLE gtest20b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest20b (a) VALUES (10); +INSERT INTO gtest20b (a) VALUES (30); +ALTER TABLE gtest20b ADD CONSTRAINT chk CHECK (b < 50) NOT VALID; +ALTER TABLE gtest20b VALIDATE CONSTRAINT chk; -- fails on existing row + +-- not-null constraints +CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED NOT NULL); +INSERT INTO gtest21a (a) VALUES (1); -- ok +INSERT INTO gtest21a (a) VALUES (0); -- violates constraint + +CREATE TABLE gtest21b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED); +ALTER TABLE gtest21b ALTER COLUMN b SET NOT NULL; +INSERT INTO gtest21b (a) VALUES (1); -- ok +INSERT INTO gtest21b (a) VALUES (0); -- violates constraint +ALTER TABLE gtest21b ALTER COLUMN b DROP NOT NULL; +INSERT INTO gtest21b (a) VALUES (0); -- ok now + +-- index constraints +CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) STORED UNIQUE); +INSERT INTO gtest22a VALUES (2); +INSERT INTO gtest22a VALUES (3); +INSERT INTO gtest22a VALUES (4); +CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) STORED, PRIMARY KEY (a, b)); +INSERT INTO gtest22b VALUES (2); +INSERT INTO gtest22b VALUES (2); + +-- indexes +CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +CREATE INDEX gtest22c_b_idx ON gtest22c (b); +CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); +CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; +\d gtest22c + +INSERT INTO gtest22c VALUES (1), (2), (3); +SET enable_seqscan TO off; +SET enable_bitmapscan TO off; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4; +SELECT * FROM gtest22c WHERE b = 4; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6; +SELECT * FROM gtest22c WHERE b * 3 = 6; +EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; +SELECT * FROM gtest22c WHERE a = 1 AND b > 0; +RESET enable_seqscan; +RESET enable_bitmapscan; + +-- foreign keys +CREATE TABLE gtest23a (x int PRIMARY KEY, y int); +INSERT INTO gtest23a VALUES (1, 11), (2, 22), (3, 33); + +CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON UPDATE CASCADE); -- error +CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON DELETE SET NULL); -- error + +CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x)); +\d gtest23b + +INSERT INTO gtest23b VALUES (1); -- ok +INSERT INTO gtest23b VALUES (5); -- error + +DROP TABLE gtest23b; +DROP TABLE gtest23a; + +CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) STORED, PRIMARY KEY (y)); +INSERT INTO gtest23p VALUES (1), (2), (3); + +CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y)); +INSERT INTO gtest23q VALUES (1, 2); -- ok +INSERT INTO gtest23q VALUES (2, 5); -- error + +-- domains +CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10); +CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest24 (a) VALUES (4); -- ok +INSERT INTO gtest24 (a) VALUES (6); -- error + +-- typed tables (currently not supported) +CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint); +CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) STORED); +DROP TYPE gtest_type CASCADE; + +-- table partitions (currently not supported) +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); +CREATE TABLE gtest_child PARTITION OF gtest_parent ( + f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 2) STORED +) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error +DROP TABLE gtest_parent; + +-- partitioned table +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f1); +CREATE TABLE gtest_child PARTITION OF gtest_parent FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1); +SELECT * FROM gtest_parent; +SELECT * FROM gtest_child; +DROP TABLE gtest_parent; + +-- generated columns in partition key (not allowed) +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); + +-- ALTER TABLE ... ADD COLUMN +CREATE TABLE gtest25 (a int PRIMARY KEY); +INSERT INTO gtest25 VALUES (3), (4); +ALTER TABLE gtest25 ADD COLUMN b int GENERATED ALWAYS AS (a * 3) STORED; +SELECT * FROM gtest25 ORDER BY a; +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4) STORED; -- error +ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error + +-- ALTER TABLE ... ALTER COLUMN +CREATE TABLE gtest27 ( + a int, + b int GENERATED ALWAYS AS (a * 2) STORED +); +INSERT INTO gtest27 (a) VALUES (3), (4); +ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error +ALTER TABLE gtest27 ALTER COLUMN b TYPE numeric; +\d gtest27 +SELECT * FROM gtest27; +ALTER TABLE gtest27 ALTER COLUMN b TYPE boolean USING b <> 0; -- error + +ALTER TABLE gtest27 ALTER COLUMN b DROP DEFAULT; -- error +\d gtest27 + +-- triggers +CREATE TABLE gtest26 ( + a int PRIMARY KEY, + b int GENERATED ALWAYS AS (a * 2) STORED +); + +CREATE FUNCTION gtest_trigger_func() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + IF tg_op IN ('DELETE', 'UPDATE') THEN + RAISE INFO '%: %: old = %', TG_NAME, TG_WHEN, OLD; + END IF; + IF tg_op IN ('INSERT', 'UPDATE') THEN + RAISE INFO '%: %: new = %', TG_NAME, TG_WHEN, NEW; + END IF; + IF tg_op = 'DELETE' THEN + RETURN OLD; + ELSE + RETURN NEW; + END IF; +END +$$; + +CREATE TRIGGER gtest1 BEFORE DELETE OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (OLD.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest2a BEFORE INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.b < 0) -- error + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest2b BEFORE INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.* IS NOT NULL) -- error + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest2 BEFORE INSERT ON gtest26 + FOR EACH ROW + WHEN (NEW.a < 0) + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest3 AFTER DELETE OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (OLD.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest4 AFTER INSERT OR UPDATE ON gtest26 + FOR EACH ROW + WHEN (NEW.b < 0) -- ok + EXECUTE PROCEDURE gtest_trigger_func(); + +INSERT INTO gtest26 (a) VALUES (-2), (0), (3); +SELECT * FROM gtest26 ORDER BY a; +UPDATE gtest26 SET a = a * -2; +SELECT * FROM gtest26 ORDER BY a; +DELETE FROM gtest26 WHERE a = -6; +SELECT * FROM gtest26 ORDER BY a; + +DROP TRIGGER gtest1 ON gtest26; +DROP TRIGGER gtest2 ON gtest26; +DROP TRIGGER gtest3 ON gtest26; + +-- Check that an UPDATE of "a" fires the trigger for UPDATE OF b, per +-- SQL standard. +CREATE FUNCTION gtest_trigger_func3() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + RAISE NOTICE 'OK'; + RETURN NEW; +END +$$; + +CREATE TRIGGER gtest11 BEFORE UPDATE OF b ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func3(); + +UPDATE gtest26 SET a = 1 WHERE a = 0; + +DROP TRIGGER gtest11 ON gtest26; +TRUNCATE gtest26; + +-- check that modifications of stored generated columns in triggers do +-- not get propagated +CREATE FUNCTION gtest_trigger_func4() RETURNS trigger + LANGUAGE plpgsql +AS $$ +BEGIN + NEW.a = 10; + NEW.b = 300; + RETURN NEW; +END; +$$; + +CREATE TRIGGER gtest12_01 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func(); + +CREATE TRIGGER gtest12_02 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func4(); + +CREATE TRIGGER gtest12_03 BEFORE UPDATE ON gtest26 + FOR EACH ROW + EXECUTE PROCEDURE gtest_trigger_func(); + +INSERT INTO gtest26 (a) VALUES (1); +UPDATE gtest26 SET a = 11 WHERE a = 1; +SELECT * FROM gtest26 ORDER BY a; + +-- LIKE INCLUDING GENERATED and dropped column handling +CREATE TABLE gtest28a ( + a int, + b int, + c int, + x int GENERATED ALWAYS AS (b * 2) STORED +); + +ALTER TABLE gtest28a DROP COLUMN a; + +CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED); + +\d gtest28* diff --git a/src/test/subscription/t/011_generated.pl b/src/test/subscription/t/011_generated.pl new file mode 100644 index 0000000000..f7456e9216 --- /dev/null +++ b/src/test/subscription/t/011_generated.pl @@ -0,0 +1,65 @@ +# Test generated columns +use strict; +use warnings; +use PostgresNode; +use TestLib; +use Test::More tests => 2; + +# setup + +my $node_publisher = get_new_node('publisher'); +$node_publisher->init(allows_streaming => 'logical'); +$node_publisher->start; + +my $node_subscriber = get_new_node('subscriber'); +$node_subscriber->init(allows_streaming => 'logical'); +$node_subscriber->start; + +my $publisher_connstr = $node_publisher->connstr . ' dbname=postgres'; + +$node_publisher->safe_psql('postgres', + "CREATE TABLE tab1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED)"); + +$node_subscriber->safe_psql('postgres', + "CREATE TABLE tab1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 22) STORED)"); + +# data for initial sync + +$node_publisher->safe_psql('postgres', + "INSERT INTO tab1 (a) VALUES (1), (2), (3)"); + +$node_publisher->safe_psql('postgres', + "CREATE PUBLICATION pub1 FOR ALL TABLES"); +$node_subscriber->safe_psql('postgres', + "CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr' PUBLICATION pub1" +); + +# Wait for initial sync of all subscriptions +my $synced_query = + "SELECT count(1) = 0 FROM pg_subscription_rel WHERE srsubstate NOT IN ('r', 's');"; +$node_subscriber->poll_query_until('postgres', $synced_query) + or die "Timed out while waiting for subscriber to synchronize data"; + +my $result = $node_subscriber->safe_psql('postgres', + "SELECT a, b FROM tab1"); +is($result, qq(1|22 +2|44 +3|66), 'generated columns initial sync'); + +# data to replicate + +$node_publisher->safe_psql('postgres', + "INSERT INTO tab1 VALUES (4), (5)"); + +$node_publisher->safe_psql('postgres', + "UPDATE tab1 SET a = 6 WHERE a = 5"); + +$node_publisher->wait_for_catchup('sub1'); + +$result = $node_subscriber->safe_psql('postgres', + "SELECT a, b FROM tab1"); +is($result, qq(1|22 +2|44 +3|66 +4|88 +6|132), 'generated columns replicated'); base-commit: c8c885b7a5c8c1175288de1d8aaec3b4ae9050e1 -- 2.21.0 --------------A25BEA11677B46AF0D81EBB9-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v7 3/6] Document historic behavior about hiding directories and special files @ 2020-03-06 23:12 Justin Pryzby <pryzbyj@telsasoft.com> 0 siblings, 0 replies; 114+ messages in thread From: Justin Pryzby @ 2020-03-06 23:12 UTC (permalink / raw) Should backpatch to v12: tmpdir --- doc/src/sgml/func.sgml | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 4c0ea5ab3f..fc4d7f0f78 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -21489,6 +21489,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup()); default only superusers and members of the <literal>pg_monitor</literal> role can use this function. Access may be granted to others using <command>GRANT</command>. + Filenames beginning with a dot, directories, and other special files are not shown. </para> <indexterm> -- 2.17.0 --jKBxcB1XkHIR0Eqt Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v7-0004-pg_ls_tmpdir-to-show-directories.patch" ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v9 2/4] Add function for removing arbitrary nodes in binaryheap. @ 2023-07-20 16:52 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2023-07-20 16:52 UTC (permalink / raw) This commit introduces binaryheap_remove_node(), which can be used to remove any node from a binary heap. The implementation is straightforward. The target node is replaced with the last node in the heap, and then we sift as needed to preserve the heap property. This new function is intended for use in a follow-up commit that will improve the performance of pg_restore. Reviewed-by: Tom Lane Discussion: https://postgr.es/m/3612876.1689443232%40sss.pgh.pa.us --- src/common/binaryheap.c | 29 +++++++++++++++++++++++++++++ src/include/lib/binaryheap.h | 3 +++ 2 files changed, 32 insertions(+) diff --git a/src/common/binaryheap.c b/src/common/binaryheap.c index 39a8243a6d..19e095f1fb 100644 --- a/src/common/binaryheap.c +++ b/src/common/binaryheap.c @@ -215,6 +215,35 @@ binaryheap_remove_first(binaryheap *heap) return result; } +/* + * binaryheap_remove_node + * + * Removes the nth (zero based) node from the heap. The caller must ensure + * that there are at least (n + 1) nodes in the heap. O(log n) worst case. + */ +void +binaryheap_remove_node(binaryheap *heap, int n) +{ + int cmp; + + Assert(!binaryheap_empty(heap) && heap->bh_has_heap_property); + Assert(n >= 0 && n < heap->bh_size); + + /* compare last node to the one that is being removed */ + cmp = heap->bh_compare(heap->bh_nodes[--heap->bh_size], + heap->bh_nodes[n], + heap->bh_arg); + + /* remove the last node, placing it in the vacated entry */ + heap->bh_nodes[n] = heap->bh_nodes[heap->bh_size]; + + /* sift as needed to preserve the heap property */ + if (cmp > 0) + sift_up(heap, n); + else if (cmp < 0) + sift_down(heap, n); +} + /* * binaryheap_replace_first * diff --git a/src/include/lib/binaryheap.h b/src/include/lib/binaryheap.h index 3647aeae65..9525dcaec4 100644 --- a/src/include/lib/binaryheap.h +++ b/src/include/lib/binaryheap.h @@ -59,8 +59,11 @@ extern void binaryheap_build(binaryheap *heap); extern void binaryheap_add(binaryheap *heap, bh_node_type d); extern bh_node_type binaryheap_first(binaryheap *heap); extern bh_node_type binaryheap_remove_first(binaryheap *heap); +extern void binaryheap_remove_node(binaryheap *heap, int n); extern void binaryheap_replace_first(binaryheap *heap, bh_node_type d); #define binaryheap_empty(h) ((h)->bh_size == 0) +#define binaryheap_size(h) ((h)->bh_size) +#define binaryheap_get_node(h, n) ((h)->bh_nodes[n]) #endif /* BINARYHEAP_H */ -- 2.25.1 --CE+1k2dSO48ffgeK Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v9-0003-Convert-pg_restore-s-ready_list-to-a-priority-que.patch" ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 2/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 7 +- src/interfaces/libpq/fe-lobj.c | 672 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 22 +- src/tools/pgindent/typedefs.list | 1 + 4 files changed, 319 insertions(+), 383 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5e41c21c6f6..9d0e0fd6798 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,9 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; + conn->lobjprepmap = 0; + conn->lo_dealloc_cb_set = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..0d3d7c1c3ed 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,116 @@ #define LO_BUFSIZE 8192 +typedef enum PGlobjfunctype +{ + LO_OPEN, + LO_CLOSE, + LO_CREAT, + LO_CREATE, + LO_UNLINK, + LO_LSEEK, + LO_LSEEK64, + LO_TELL, + LO_TELL64, + LO_TRUNCATE, + LO_TRUNCATE64, + LO_READ, + LO_WRITE, +} PGlobjfunctype; + +typedef struct PGlobjfuncs +{ + char *name; + char *query; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + [LO_OPEN] = { + "libpq_internal_lo_open", + "SELECT pg_catalog.lo_open($1::pg_catalog.oid, $2::pg_catalog.int4)" + }, + [LO_CLOSE] = { + "libpq_internal_lo_close", + "SELECT pg_catalog.lo_close($1::pg_catalog.int4)" + }, + [LO_CREAT] = { + "libpq_internal_lo_creat", + "SELECT pg_catalog.lo_creat($1::pg_catalog.int4)" + }, + [LO_CREATE] = { + "libpq_internal_lo_create", + "SELECT pg_catalog.lo_create($1::pg_catalog.oid)", + 80100 + }, + [LO_UNLINK] = { + "libpq_internal_lo_unlink", + "SELECT pg_catalog.lo_unlink($1::pg_catalog.oid)" + }, + [LO_LSEEK] = { + "libpq_internal_lo_lseek", + "SELECT pg_catalog.lo_lseek($1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4)" + }, + [LO_LSEEK64] = { + "libpq_internal_lo_lseek64", + "SELECT pg_catalog.lo_lseek64($1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4)", + 90300 + }, + [LO_TELL] = { + "libpq_internal_lo_tell", + "SELECT pg_catalog.lo_tell($1::pg_catalog.int4)" + }, + [LO_TELL64] = { + "libpq_internal_lo_tell64", + "SELECT pg_catalog.lo_tell64($1::pg_catalog.int4)", + 90300 + }, + [LO_TRUNCATE] = { + "libpq_internal_lo_truncate", + "SELECT pg_catalog.lo_truncate($1::pg_catalog.int4, $2::pg_catalog.int4)", + 80300 + }, + [LO_TRUNCATE64] = { + "libpq_internal_lo_truncate64", + "SELECT pg_catalog.lo_truncate64($1::pg_catalog.int4, $2::pg_catalog.int8)", + 90300 + }, + [LO_READ] = { + "libpq_internal_loread", + "SELECT pg_catalog.loread($1::pg_catalog.int4, $2::pg_catalog.int4)" + }, + [LO_WRITE] = { + "libpq_internal_lowrite", + "SELECT pg_catalog.lowrite($1::pg_catalog.int4, $2::pg_catalog.bytea)" + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} + +static PGresult * +lo_exec(PGconn *conn, PGlobjfunctype type, int nargs, char **argv, + const int *argLens, const int *argFmts) +{ + if (conn->lobjprepared) + return PQexecPrepared(conn, lobjfuncs[type].name, nargs, + (const char *const *) argv, argLens, argFmts, 1); + else + return PQexecParams(conn, lobjfuncs[type].query, nargs, NULL, + (const char *const *) argv, argLens, argFmts, 1); +} /* * lo_open @@ -57,26 +163,26 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_OPEN, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +201,24 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = lo_exec(conn, LO_CLOSE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +237,19 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +269,18 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); - - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +299,34 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFmts[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + len = pg_hton64(len); + argv[1] = (char *) &len; - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TRUNCATE64, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +346,11 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int len32 = len; + int argLens[] = {4, 4}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +367,22 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len32 = pg_hton32(len32); + argv[1] = (char *) &len32; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_READ, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +402,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFmts[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +423,17 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + argv[1] = unconstify(char *, buf); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_WRITE, 2, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +449,30 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFmts[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_LSEEK, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +488,37 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFmts[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_LSEEK64, 3, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -437,23 +538,24 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = lo_exec(conn, LO_CREAT, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,31 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = lo_exec(conn, LO_CREATE, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +617,23 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_TELL, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +650,30 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = lo_exec(conn, LO_TELL64, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +690,24 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFmts[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = lo_exec(conn, LO_UNLINK, 1, argv, argLens, argFmts); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -829,6 +931,30 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) return result; } +/* + * LO prepared statement deallocation callback. Resets conn->lobjprepared and + * the corresponding bits in conn->lobjprepmap so the next call to + * lo_initialize() re-prepares as needed. + */ +static void +lo_dealloc_callback(PGconn *conn, const char *name) +{ + if (name[0] == '\0') + { + conn->lobjprepared = false; + conn->lobjprepmap = 0; + return; + } + + for (int i = 0; i < lengthof(lobjfuncs); i++) + { + if (strcmp(name, lobjfuncs[i].name) != 0) + continue; + + conn->lobjprepmap &= ~(1 << i); + break; + } +} /* * lo_initialize @@ -836,19 +962,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -857,208 +976,41 @@ lo_initialize(PGconn *conn) pqClearConnErrorState(conn); /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) - { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); - - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + /* Use PQexecParams() on servers that don't have dealloc notifications */ + if (PQfullProtocolVersion(conn) < 30003) + return 0; - res = PQexec(conn, query); - if (res == NULL) + if (!conn->lo_dealloc_cb_set) { - free(lobjfuncs); - return -1; + PQaddPrepStmtDeallocCallback(conn, lo_dealloc_callback); + conn->lo_dealloc_cb_set = true; } - if (res->resultStatus != PGRES_TUPLES_OK) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + PGresult *res; - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + if (conn->lobjprepmap & (1 << i)) + continue; - PQclear(res); + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); - return -1; + res = PQprepare(conn, lobjfuncs[i].name, lobjfuncs[i].query, 0, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + libpq_append_conn_error(conn, "query to prepare large object statements failed"); + return -1; + } + + PQclear(res); + conn->lobjprepmap |= (1 << i); } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7eca941ddcc..7a32c14de5e 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -564,7 +546,9 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ + uint32 lobjprepmap; /* bitmap of prepared LO statements */ + bool lo_dealloc_cb_set; /* whether prep stmt dealloc callback set */ pg_prng_state prng_state; /* prng state for load balancing connections */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 8cf40c87043..86b90010d2a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1983,6 +1983,7 @@ PGcmdQueueEntry PGconn PGdataValue PGlobjfuncs +PGlobjfunctype PGnotify PGoauthBearerRequest PGoauthBearerRequestV2 -- 2.50.1 (Apple Git-155) --wfzLmV/Wl0ehOJBp Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v2-0003-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v1 1/3] stop using PQfn() in libpq's LO interface @ 2026-05-22 17:40 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 114+ messages in thread From: Nathan Bossart @ 2026-05-22 17:40 UTC (permalink / raw) --- src/interfaces/libpq/fe-connect.c | 5 +- src/interfaces/libpq/fe-lobj.c | 656 ++++++++++++++---------------- src/interfaces/libpq/libpq-int.h | 20 +- 3 files changed, 299 insertions(+), 382 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 4272d386e64..01c9735f276 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -692,10 +692,7 @@ pqDropServerData(PGconn *conn) conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->scram_sha_256_iterations = SCRAM_SHA_256_DEFAULT_ITERATIONS; conn->sversion = 0; - - /* Drop large-object lookup data */ - free(conn->lobjfuncs); - conn->lobjfuncs = NULL; + conn->lobjprepared = false; /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c index 12a32fcbaf3..41573c22c80 100644 --- a/src/interfaces/libpq/fe-lobj.c +++ b/src/interfaces/libpq/fe-lobj.c @@ -41,10 +41,101 @@ #define LO_BUFSIZE 8192 +typedef struct PGlobjfuncs +{ + char *name; + char *args; + int nargs; + int version; +} PGlobjfuncs; + +static const PGlobjfuncs lobjfuncs[] = +{ + { + "lo_open", + "$1::pg_catalog.oid, $2::pg_catalog.int4", + 2 + }, + { + "lo_close", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_creat", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_create", + "$1::pg_catalog.oid", + 1, + 80100 + }, + { + "lo_unlink", + "$1::pg_catalog.oid", + 1 + }, + { + "lo_lseek", + "$1::pg_catalog.int4, $2::pg_catalog.int4, $3::pg_catalog.int4", + 3 + }, + { + "lo_lseek64", + "$1::pg_catalog.int4, $2::pg_catalog.int8, $3::pg_catalog.int4", + 3, + 90300 + }, + { + "lo_tell", + "$1::pg_catalog.int4", + 1 + }, + { + "lo_tell64", + "$1::pg_catalog.int4", + 1, + 90300 + }, + { + "lo_truncate", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2, + 80300 + }, + { + "lo_truncate64", + "$1::pg_catalog.int4, $2::pg_catalog.int8", + 2, + 90300 + }, + { + "loread", + "$1::pg_catalog.int4, $2::pg_catalog.int4", + 2 + }, + { + "lowrite", + "$1::pg_catalog.int4, $2::pg_catalog.bytea", + 2 + } +}; + static int lo_initialize(PGconn *conn); static Oid lo_import_internal(PGconn *conn, const char *filename, Oid oid); -static int64_t lo_hton64(int64_t host64); -static int64_t lo_ntoh64(int64_t net64); + +static bool +lo_result_is_valid(const PGresult *res, int len) +{ + return PQresultStatus(res) == PGRES_TUPLES_OK && + PQntuples(res) == 1 && + PQnfields(res) == 1 && + PQgetisnull(res, 0, 0) == 0 && + PQfformat(res, 0) == 1 && + (len == -1 || PQgetlength(res, 0, 0) == len); +} /* * lo_open @@ -57,26 +148,28 @@ int lo_open(PGconn *conn, Oid lobjId, int mode) { int fd; - int result_len; - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + mode = pg_hton32(mode); + argv[1] = (char *) &mode; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = mode; + res = PQexecPrepared(conn, "libpq_internal_lo_open", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_open, &fd, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(fd))) { + memcpy(&fd, PQgetvalue(res, 0, 0), sizeof(fd)); PQclear(res); - return fd; + return pg_ntoh32(fd); } else { @@ -95,23 +188,26 @@ lo_open(PGconn *conn, Oid lobjId, int mode) int lo_close(PGconn *conn, int fd) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_close, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_close", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -130,18 +226,18 @@ lo_close(PGconn *conn, int fd) int lo_truncate(PGconn *conn, int fd, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - /* Must check this on-the-fly because it's not there pre-8.3 */ - if (conn->lobjfuncs->fn_lo_truncate == 0) + if (PQserverVersion(conn) < 80300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate"); return -1; } @@ -161,21 +257,20 @@ lo_truncate(PGconn *conn, int fd, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + len = pg_hton32(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "libpq_internal_lo_truncate", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -194,37 +289,36 @@ lo_truncate(PGconn *conn, int fd, size_t len) int lo_truncate64(PGconn *conn, int fd, int64_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 8}; + int argFormats[] = {1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_truncate64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_truncate64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - len = lo_hton64(len); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &len; + len = pg_hton64(len); + argv[1] = (char *) &len; - res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate64, - &retval, &result_len, 1, argv, 2); + res = PQexecPrepared(conn, "ligpq_internal_lo_truncate64", 2, + (const char *const *) argv, argLens, argFormats, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -244,9 +338,10 @@ lo_truncate64(PGconn *conn, int fd, int64_t len) int lo_read(PGconn *conn, int fd, char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, 4}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; @@ -263,18 +358,24 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + len = pg_hton32(len); + argv[1] = (char *) &len; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = (int) len; + res = PQexecPrepared(conn, "libpq_internal_loread", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_read, - (void *) buf, len, &result_len, 0, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, -1)) { + int result_len = PQgetlength(res, 0, 0); + + if (result_len > len) + result_len = -1; + else + memcpy(buf, PQgetvalue(res, 0, 0), result_len); + PQclear(res); return result_len; } @@ -294,9 +395,10 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len) int lo_write(PGconn *conn, int fd, const char *buf, size_t len) { - PQArgBlock argv[2]; + char *argv[2]; + int argLens[] = {4, len}; + int argFormats[] = {1, 1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) @@ -314,20 +416,19 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + argv[1] = unconstify(char *, buf); - argv[1].isint = 0; - argv[1].len = (int) len; - argv[1].u.ptr = (int *) unconstify(char *, buf); + res = PQexecPrepared(conn, "libpq_internal_lowrite", 2, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_write, - &retval, &result_len, 1, argv, 2); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -343,32 +444,32 @@ lo_write(PGconn *conn, int fd, const char *buf, size_t len) int lo_lseek(PGconn *conn, int fd, int offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 4, 4}; + int argFormats[] = {1, 1, 1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - argv[1].isint = 1; - argv[1].len = 4; - argv[1].u.integer = offset; + offset = pg_hton32(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek, - &retval, &result_len, 1, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lseek", 3, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -384,40 +485,38 @@ lo_lseek(PGconn *conn, int fd, int offset, int whence) int64_t lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) { - PQArgBlock argv[3]; + char *argv[3]; + int argLens[] = {4, 8, 4}; + int argFormats[3] = {1, 1, 1}; PGresult *res; int64 retval; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_lseek64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_lseek64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - offset = lo_hton64(offset); - argv[1].isint = 0; - argv[1].len = 8; - argv[1].u.ptr = (int *) &offset; + offset = pg_hton64(offset); + argv[1] = (char *) &offset; - argv[2].isint = 1; - argv[2].len = 4; - argv[2].u.integer = whence; + whence = pg_hton32(whence); + argv[2] = (char *) &whence; - res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 3); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + res = PQexecPrepared(conn, "ligpq_internal_lo_lseek64", 3, + (const char *const *) argv, argLens, argFormats, 1); + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh32(retval); } else { @@ -437,23 +536,26 @@ lo_lseek64(PGconn *conn, int fd, int64_t offset, int whence) Oid lo_creat(PGconn *conn, int mode) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = mode; - res = PQfn(conn, conn->lobjfuncs->fn_lo_creat, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + mode = pg_hton32(mode); + argv[0] = (char *) &mode; + + res = PQexecPrepared(conn, "libpq_internal_lo_creat", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -473,31 +575,33 @@ lo_creat(PGconn *conn, int mode) Oid lo_create(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; int retval; - int result_len; if (lo_initialize(conn) < 0) return InvalidOid; - /* Must check this on-the-fly because it's not there pre-8.1 */ - if (conn->lobjfuncs->fn_lo_create == 0) + if (PQserverVersion(conn) < 80100) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support function \"%s\"", "lo_create"); return InvalidOid; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; - res = PQfn(conn, conn->lobjfuncs->fn_lo_create, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_create", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return (Oid) retval; + return (Oid) pg_ntoh32(retval); } else { @@ -515,23 +619,25 @@ int lo_tell(PGconn *conn, int fd) { int retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; - res = PQfn(conn, conn->lobjfuncs->fn_lo_tell, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + res = PQexecPrepared(conn, "libpq_internal_lo_tell", 1, + (const char *const *) argv, argLens, argFormats, 1); + + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -548,30 +654,32 @@ int64_t lo_tell64(PGconn *conn, int fd) { int64 retval; - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; if (lo_initialize(conn) < 0) return -1; - if (conn->lobjfuncs->fn_lo_tell64 == 0) + if (PQserverVersion(conn) < 90300) { - libpq_append_conn_error(conn, "cannot determine OID of function %s", + libpq_append_conn_error(conn, "server does not support functions \"%s\"", "lo_tell64"); return -1; } - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = fd; + fd = pg_hton32(fd); + argv[0] = (char *) &fd; + + res = PQexecPrepared(conn, "libpq_internal_lo_tell64", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64, - (void *) &retval, sizeof(retval), &result_len, 0, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return lo_ntoh64(retval); + return pg_ntoh64(retval); } else { @@ -588,24 +696,26 @@ lo_tell64(PGconn *conn, int fd) int lo_unlink(PGconn *conn, Oid lobjId) { - PQArgBlock argv[1]; + char *argv[1]; + int argLens[] = {4}; + int argFormats[] = {1}; PGresult *res; - int result_len; int retval; if (lo_initialize(conn) < 0) return -1; - argv[0].isint = 1; - argv[0].len = 4; - argv[0].u.integer = lobjId; + lobjId = pg_hton32(lobjId); + argv[0] = (char *) &lobjId; + + res = PQexecPrepared(conn, "libpq_internal_lo_unlink", 1, + (const char *const *) argv, argLens, argFormats, 1); - res = PQfn(conn, conn->lobjfuncs->fn_lo_unlink, - &retval, &result_len, 1, argv, 1); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + if (lo_result_is_valid(res, sizeof(retval))) { + memcpy(&retval, PQgetvalue(res, 0, 0), sizeof(retval)); PQclear(res); - return retval; + return pg_ntoh32(retval); } else { @@ -836,19 +946,12 @@ lo_export(PGconn *conn, Oid lobjId, const char *filename) * Initialize for a new large-object operation on an existing connection. * Return 0 if OK, -1 on failure. * - * If we haven't previously done so, we collect the function OIDs from - * pg_proc for all functions that are required for large object operations. + * If we haven't previously done so, we prepared statements for all the + * functions that are required for large object operations. */ static int lo_initialize(PGconn *conn) { - PGresult *res; - PGlobjfuncs *lobjfuncs; - int n; - const char *query; - const char *fname; - Oid foid; - /* Nothing we can do with no connection */ if (conn == NULL) return -1; @@ -856,209 +959,44 @@ lo_initialize(PGconn *conn) /* Since this is the beginning of a query cycle, reset the error state */ pqClearConnErrorState(conn); - /* Nothing else to do if we already collected info */ - if (conn->lobjfuncs != NULL) + /* Nothing else to do if we already prepared the statements */ + if (conn->lobjprepared) return 0; - /* - * Allocate the structure to hold the function OIDs. We don't store it - * into the PGconn until it's successfully filled. - */ - lobjfuncs = (PGlobjfuncs *) malloc(sizeof(PGlobjfuncs)); - if (lobjfuncs == NULL) + for (int i = 0; i < lengthof(lobjfuncs); i++) { - libpq_append_conn_error(conn, "out of memory"); - return -1; - } - MemSet(lobjfuncs, 0, sizeof(PGlobjfuncs)); + PGresult *res; + ExecStatusType status; + PQExpBufferData pname; + PQExpBufferData pquery; - /* - * Execute the query to get all the functions at once. (Not all of them - * may exist in older server versions.) - */ - query = "select proname, oid from pg_catalog.pg_proc " - "where proname in (" - "'lo_open', " - "'lo_close', " - "'lo_creat', " - "'lo_create', " - "'lo_unlink', " - "'lo_lseek', " - "'lo_lseek64', " - "'lo_tell', " - "'lo_tell64', " - "'lo_truncate', " - "'lo_truncate64', " - "'loread', " - "'lowrite') " - "and pronamespace = (select oid from pg_catalog.pg_namespace " - "where nspname = 'pg_catalog')"; + if (PQserverVersion(conn) < lobjfuncs[i].version) + continue; - res = PQexec(conn, query); - if (res == NULL) - { - free(lobjfuncs); - return -1; - } + initPQExpBuffer(&pname); + initPQExpBuffer(&pquery); - if (res->resultStatus != PGRES_TUPLES_OK) - { - free(lobjfuncs); - PQclear(res); - libpq_append_conn_error(conn, "query to initialize large object functions did not return data"); - return -1; - } + appendPQExpBuffer(&pname, "libpq_internal_%s", + lobjfuncs[i].name); + appendPQExpBuffer(&pquery, "SELECT pg_catalog.%s(%s)", + lobjfuncs[i].name, lobjfuncs[i].args); - /* - * Examine the result and put the OID's into the struct - */ - for (n = 0; n < PQntuples(res); n++) - { - fname = PQgetvalue(res, n, 0); - foid = (Oid) atoi(PQgetvalue(res, n, 1)); - if (strcmp(fname, "lo_open") == 0) - lobjfuncs->fn_lo_open = foid; - else if (strcmp(fname, "lo_close") == 0) - lobjfuncs->fn_lo_close = foid; - else if (strcmp(fname, "lo_creat") == 0) - lobjfuncs->fn_lo_creat = foid; - else if (strcmp(fname, "lo_create") == 0) - lobjfuncs->fn_lo_create = foid; - else if (strcmp(fname, "lo_unlink") == 0) - lobjfuncs->fn_lo_unlink = foid; - else if (strcmp(fname, "lo_lseek") == 0) - lobjfuncs->fn_lo_lseek = foid; - else if (strcmp(fname, "lo_lseek64") == 0) - lobjfuncs->fn_lo_lseek64 = foid; - else if (strcmp(fname, "lo_tell") == 0) - lobjfuncs->fn_lo_tell = foid; - else if (strcmp(fname, "lo_tell64") == 0) - lobjfuncs->fn_lo_tell64 = foid; - else if (strcmp(fname, "lo_truncate") == 0) - lobjfuncs->fn_lo_truncate = foid; - else if (strcmp(fname, "lo_truncate64") == 0) - lobjfuncs->fn_lo_truncate64 = foid; - else if (strcmp(fname, "loread") == 0) - lobjfuncs->fn_lo_read = foid; - else if (strcmp(fname, "lowrite") == 0) - lobjfuncs->fn_lo_write = foid; - } + res = PQprepare(conn, pname.data, pquery.data, + lobjfuncs[i].nargs, NULL); + status = PQresultStatus(res); + if (res) + PQclear(res); - PQclear(res); + termPQExpBuffer(&pname); + termPQExpBuffer(&pquery); - /* - * Finally check that we got all required large object interface functions - * (ones that have been added later than the stone age are instead checked - * only if used) - */ - if (lobjfuncs->fn_lo_open == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_open"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_close == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_close"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_creat == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_creat"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_unlink == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_unlink"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_lseek == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_lseek"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_tell == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lo_tell"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_read == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "loread"); - free(lobjfuncs); - return -1; - } - if (lobjfuncs->fn_lo_write == 0) - { - libpq_append_conn_error(conn, "cannot determine OID of function %s", - "lowrite"); - free(lobjfuncs); + if (status == PGRES_COMMAND_OK) + continue; + + libpq_append_conn_error(conn, "query to prepare large object statements failed"); return -1; } - /* - * Put the structure into the connection control - */ - conn->lobjfuncs = lobjfuncs; + conn->lobjprepared = true; return 0; } - -/* - * lo_hton64 - * converts a 64-bit integer from host byte order to network byte order - */ -static int64_t -lo_hton64(int64_t host64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - uint32 t; - - /* High order half first, since we're doing MSB-first */ - t = (uint32) (host64 >> 32); - swap.i32[0] = pg_hton32(t); - - /* Now the low order half */ - t = (uint32) host64; - swap.i32[1] = pg_hton32(t); - - return swap.i64; -} - -/* - * lo_ntoh64 - * converts a 64-bit integer from network byte order to host byte order - */ -static int64_t -lo_ntoh64(int64_t net64) -{ - union - { - int64 i64; - uint32 i32[2]; - } swap; - int64 result; - - swap.i64 = net64; - - result = (uint32) pg_ntoh32(swap.i32[0]); - result <<= 32; - result |= (uint32) pg_ntoh32(swap.i32[1]); - - return result; -} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 461b39620c3..5248e895bc0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -275,24 +275,6 @@ typedef struct pgParameterStatus /* Note: name and value are stored in same malloc block as struct is */ } pgParameterStatus; -/* large-object-access data ... allocated only if large-object code is used. */ -typedef struct pgLobjfuncs -{ - Oid fn_lo_open; /* OID of backend function lo_open */ - Oid fn_lo_close; /* OID of backend function lo_close */ - Oid fn_lo_creat; /* OID of backend function lo_creat */ - Oid fn_lo_create; /* OID of backend function lo_create */ - Oid fn_lo_unlink; /* OID of backend function lo_unlink */ - Oid fn_lo_lseek; /* OID of backend function lo_lseek */ - Oid fn_lo_lseek64; /* OID of backend function lo_lseek64 */ - Oid fn_lo_tell; /* OID of backend function lo_tell */ - Oid fn_lo_tell64; /* OID of backend function lo_tell64 */ - Oid fn_lo_truncate; /* OID of backend function lo_truncate */ - Oid fn_lo_truncate64; /* OID of function lo_truncate64 */ - Oid fn_lo_read; /* OID of backend function LOread */ - Oid fn_lo_write; /* OID of backend function LOwrite */ -} PGlobjfuncs; - /* * PGdataValue represents a data field value being passed to a row processor. * It could be either text or binary data; text data is not zero-terminated. @@ -562,7 +544,7 @@ struct pg_conn PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ - PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ + bool lobjprepared; /* whether LO statements have been prepared */ pg_prng_state prng_state; /* prng state for load balancing connections */ -- 2.50.1 (Apple Git-155) --NDf4SqMA2rj1ZNJ4 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=v1-0002-remove-support-for-PQfn.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
end of thread, other threads:[~2026-05-22 17:40 UTC | newest] Thread overview: 114+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2019-03-17 15:58 [PATCH v9] Generated columns Peter Eisentraut <peter@eisentraut.org> 2019-03-26 13:31 [PATCH v10] Generated columns Peter Eisentraut <peter@eisentraut.org> 2020-03-06 23:12 [PATCH v7 3/6] Document historic behavior about hiding directories and special files Justin Pryzby <pryzbyj@telsasoft.com> 2023-07-20 16:52 [PATCH v9 2/4] Add function for removing arbitrary nodes in binaryheap. Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v2 2/3] stop using PQfn() in libpq's LO interface Nathan Bossart <nathan@postgresql.org> 2026-05-22 17:40 [PATCH v1 1/3] stop using PQfn() in libpq's LO interface 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