[SCM] PostGIS branch master updated. 3.6.0rc2-638-g0482d073a
git at osgeo.org
git at osgeo.org
Sun Jun 21 06:06:02 PDT 2026
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "PostGIS".
The branch, master has been updated
via 0482d073a2d174a544428664fa7cebcd36b7d96a (commit)
via 37db33a47b8d380d6b58f8fcdf4824bfa0f2c847 (commit)
from 986bb6a1a6a133be10c234b8b04bd7bf1025fa05 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 0482d073a2d174a544428664fa7cebcd36b7d96a
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sun Jun 21 00:56:01 2026 +0400
Expose loader action long options
Add a shared loader action vocabulary and normalize raster2pgsql and shp2pgsql options into final LoaderPlan values. Short mode flags remain compatibility presets, and long action flags overlay the selected preset, defaulting to create/load when no mode is specified.
Support idempotent table and index creation with the single --if-not-exists modifier on active creation actions. Keep --no-transaction as the public transaction modifier, reject invalid append/drop/load-without-create action sets, and preserve stable names for idempotent index creation.
Inline shp2pgsql geometry/geography column DDL so table creation no longer needs AddGeometryColumn or DropGeometryColumn helper calls.
diff --git a/NEWS b/NEWS
index 495b17930..a9843c1aa 100644
--- a/NEWS
+++ b/NEWS
@@ -25,6 +25,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
(Darafei Praliaskouski)
- #2935, shp2pgsql --drop-table can emit DROP TABLE before prepare output
(Darafei Praliaskouski)
+ - raster2pgsql and shp2pgsql expose loader actions as long options
+ (Darafei Praliaskouski)
- #4208, Add single-geometry variants of ST_MaxDistance and ST_LongestLine
(Darafei Praliaskouski)
- [topology] FindVertexSegmentPairsBelowDistance function (Sandro Santilli)
@@ -67,7 +69,7 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
(Sandro Santilli)
- #3743, [raster] Document and test raster2pgsql -s FROM_SRID:SRID
reprojection support (Darafei Praliaskouski)
- - #4659, [raster] Add raster2pgsql --if-not-exists creation modifier
+ - #4659, [raster] Add raster2pgsql and shp2pgsql long loader action options
(Darafei Praliaskouski)
- #4749, Use point-in-polygon predicate fast paths for point-only
GeometryCollections (Darafei Praliaskouski)
diff --git a/doc/man/raster2pgsql.1 b/doc/man/raster2pgsql.1
index 10adc8d9e..5fd3e3f0d 100644
--- a/doc/man/raster2pgsql.1
+++ b/doc/man/raster2pgsql.1
@@ -57,7 +57,18 @@ Prepare mode. Only emit SQL to create the table.
\fB\-\-if\-not\-exists\fR
Use IF NOT EXISTS for table creation in \-c and \-p modes. When \-I is also
specified, use IF NOT EXISTS for index creation too. This option cannot be used
-with \-a or \-d.
+with \-d. Append mode requires an explicit creation action.
+.TP
+\fB\-\-drop\-table\fR
+Drop the target table before other actions. With no mode specified, the default
+create/load actions still apply. In append mode this requires
+\-\-create\-table when data is loaded.
+.TP
+\fB\-\-create\-table\fR
+Create the target table.
+.TP
+\fB\-\-load\-data\fR
+Load raster data into the target table.
.TP
\fB\-f\fR <\fIcolumn\fR>
Specify the name of the raster column.
@@ -79,14 +90,26 @@ Wrap PostgreSQL identifiers in quotes.
\fB\-I\fR
Create a GiST spatial index on the raster column.
.TP
+\fB\-\-create\-index\fR
+Create a GiST spatial index on the raster column.
+.TP
\fB\-M\fR
Run VACUUM ANALYZE on the raster table. This is most useful when appending to an
existing table with \-a.
.TP
+\fB\-\-vacuum\fR
+Run VACUUM on the raster table.
+.TP
+\fB\-\-analyze\fR
+Run ANALYZE on the raster table.
+.TP
\fB\-C\fR
Set the standard raster constraints after the rasters are loaded. Some
constraints may fail if one or more input rasters violate the constraint.
.TP
+\fB\-\-add\-constraints\fR
+Set the standard raster constraints after the rasters are loaded.
+.TP
\fB\-x\fR
Disable setting the max extent constraint. Only applies with \-C.
.TP
@@ -118,6 +141,9 @@ Specify the output WKB format version. Only version 0 is currently supported.
Execute each statement individually instead of wrapping the load in a
transaction.
.TP
+\fB\-\-no\-transaction\fR
+Execute statements individually, without using a transaction.
+.TP
\fB\-Y\fR [<\fImax_rows_per_copy\fR>]
Use COPY statements instead of INSERT statements. If no row limit is provided,
50 rows are written per COPY.
diff --git a/doc/man/shp2pgsql.1 b/doc/man/shp2pgsql.1
index 8ccc5d1bb..c840c60f9 100644
--- a/doc/man/shp2pgsql.1
+++ b/doc/man/shp2pgsql.1
@@ -49,8 +49,15 @@ This can be used if you need to completely separate the table creation and
data loading steps.
.TP
\fB\-\-drop\-table\fR
-Emits a DROP TABLE IF EXISTS statement before table creation. This can be
-combined with \-c or \-p, but not with \-a.
+Drop the target table before the selected actions. With no mode specified, the
+default create/load actions still apply.
+.TP
+\fB\-\-create\-table\fR
+Create the target table. This can be combined with \-a to build a custom
+create-and-load action set.
+.TP
+\fB\-\-load\-data\fR
+Load Shape file rows into the target table.
.TP
\fB\-D\fR
Use the PostgreSQL "dump" format for the output data. This can be combined
@@ -66,7 +73,10 @@ Execute each statement on its own, without using a transaction.
This allows loading of the majority of good data when there are some bad
geometries that generate errors. Note that this cannot be used with the
\-D flag as the "dump" format always uses a transaction.
-.TP
+.TP
+\fB\-\-no\-transaction\fR
+Execute each statement individually, without using a transaction.
+.TP
\fB\-s\fR [<\fIFROM_SRID\fR>:]<\fISRID\fR>
Creates and populates the geometry tables with the specified SRID.
If FROM_SRID is given, the geometries will be reprojected.
@@ -79,6 +89,9 @@ lat/lon data. At the moment the only spatial reference supported is 4326.
\fB\-g\fR <\fIgeometry_column\fR>
Specify the name of the geometry column (mostly useful in append mode).
.TP
+\fB\-\-create\-index\fR
+Create a spatial index on the geometry column.
+.TP
\fB\-k\fR
Keep identifiers case (column, schema and attributes). Note that attributes
in Shapefile are usually all UPPERCASE.
@@ -112,6 +125,15 @@ If this option is used the output will be encoded in UTF-8.
\fB\-I\fR
Create a GiST index on the geometry column.
.TP
+\fB\-\-if\-not\-exists\fR
+Make active table and index creation actions use IF NOT EXISTS.
+.TP
+\fB\-\-analyze\fR
+Analyze the table after loading.
+.TP
+\fB\-\-no\-analyze\fR
+Prevent tables from being analyzed.
+.TP
\fB\-u\fR
Create the target table as UNLOGGED. This can speed up loading transient
staging data, but PostgreSQL does not preserve unlogged table contents after
diff --git a/doc/using_raster_dataman.xml b/doc/using_raster_dataman.xml
index 64973efc0..7568d3ada 100644
--- a/doc/using_raster_dataman.xml
+++ b/doc/using_raster_dataman.xml
@@ -145,8 +145,28 @@ Available GDAL raster formats:
<option>-c</option> and <option>-p</option> modes. When
<option>-I</option> is also specified, use
<literal>IF NOT EXISTS</literal> for index creation too. This
- option cannot be used with <option>-a</option> or
- <option>-d</option>.
+ option cannot be used with <option>-d</option>. Append mode
+ requires an explicit creation action.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Atomic loader actions</term>
+ <listitem>
+ <para>
+ The short modes are presets over explicit actions. The equivalent
+ long options are <option>--drop-table</option>,
+ <option>--create-table</option>, <option>--load-data</option>,
+ <option>--create-index</option>,
+ <option>--add-constraints</option>, <option>--vacuum</option>,
+ <option>--analyze</option>, and <option>--no-transaction</option>.
+ Add <option>--if-not-exists</option>
+ to make active creation actions use <literal>IF NOT EXISTS</literal>.
+ <option>--drop-table</option> emits
+ <literal>DROP TABLE IF EXISTS</literal> before the selected
+ actions. With no mode specified, the default create/load actions
+ still apply.
</para>
</listitem>
</varlistentry>
diff --git a/loader/Makefile.in b/loader/Makefile.in
index c17fbce6b..657207cad 100644
--- a/loader/Makefile.in
+++ b/loader/Makefile.in
@@ -99,7 +99,7 @@ shp2pgsql-gui.res: shp2pgsql-gui.rc shp2pgsql-gui.ico
$(LIBLWGEOM):
$(MAKE) -C ../liblwgeom
-shp2pgsql-core.o: shp2pgsql-core.c shp2pgsql-core.h shpcommon.h
+shp2pgsql-core.o: shp2pgsql-core.c shp2pgsql-core.h shpcommon.h loader_actions.h
$(CC) $(CPPFLAGS) $(CFLAGS) -c $<
pgsql2shp-core.o: pgsql2shp-core.c pgsql2shp-core.h shpcommon.h
@@ -119,7 +119,7 @@ $(SHP2PGSQL-CLI): $(SHPLIB_OBJS) shp2pgsql-core.o shp2pgsql-cli.o $(LIBLWGEOM)
$(LIBTOOL) --mode=link \
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) $(GETTEXT_LDFLAGS) $(ICONV_LDFLAGS)
-shp2pgsql-gui.o: shp2pgsql-gui.c shp2pgsql-core.h shpcommon.h
+shp2pgsql-gui.o: shp2pgsql-gui.c shp2pgsql-core.h shpcommon.h loader_actions.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(GTK_CFLAGS) $(PGSQL_FE_CPPFLAGS) -o $@ -c $<
$(SHP2PGSQL-GUI): $(SHPLIB_OBJS) shp2pgsql-core.o shp2pgsql-gui.o pgsql2shp-core.o $(LIBLWGEOM) $(GTK_WIN32_RES)
diff --git a/loader/README.shp2pgsql b/loader/README.shp2pgsql
index d5afc8e19..0fd7685d8 100644
--- a/loader/README.shp2pgsql
+++ b/loader/README.shp2pgsql
@@ -49,8 +49,18 @@ OPTIONS
rate the table creation and data loading steps.
--drop-table
- Emits a DROP TABLE IF EXISTS statement before table creation.
- This can be combined with -c or -p, but not with -a.
+ Drop the target table before the selected actions. With no mode
+ specified, the default create/load actions still apply.
+
+ --create-table
+ Create the target table. This can be combined with -a to build a
+ custom create-and-load action set.
+
+ --if-not-exists
+ Make active table and index creation actions use IF NOT EXISTS.
+
+ --load-data
+ Load Shape file rows into the target table.
-D Use the PostgreSQL "dump" format for the output data. This can
be combined with -a, -c and -d. It is much faster to load than
@@ -68,6 +78,9 @@ OPTIONS
Specify the name of the geometry column (mostly useful in append
mode).
+ --create-index
+ Create a spatial index on the geometry column.
+
-k Keep identifiers case (column, schema and attributes). Note that
attributes in Shapefile are usually all UPPERCASE.
@@ -98,6 +111,15 @@ OPTIONS
-? Display version and usage information.
+ --analyze
+ Analyze the table after loading.
+
+ --no-analyze
+ Prevent tables from being analyzed.
+
+ --no-transaction
+ Execute each statement individually, without using a transaction.
+
INSTALLATION
To compile the program from source, simply run "make" in the source
diff --git a/loader/cunit/Makefile.in b/loader/cunit/Makefile.in
index e5ee4df2f..9798d1ba1 100644
--- a/loader/cunit/Makefile.in
+++ b/loader/cunit/Makefile.in
@@ -112,6 +112,8 @@ cu_tester: $(LOADER_OBJS) $(OBJS)
$(OBJS): %.o: %.c
$(CC) $(CFLAGS) $(CUNIT_CPPFLAGS) $(GTK_CFLAGS) $(PGSQL_FE_CPPFLAGS) -c -o $@ $<
+cu_shp2pgsql.o: ../shp2pgsql-core.h ../loader_actions.h
+
# Clean target
clean:
rm -f $(OBJS)
diff --git a/loader/cunit/cu_shp2pgsql.c b/loader/cunit/cu_shp2pgsql.c
index 0eefab957..01be2be03 100644
--- a/loader/cunit/cu_shp2pgsql.c
+++ b/loader/cunit/cu_shp2pgsql.c
@@ -18,6 +18,8 @@
void test_ShpLoaderCreate(void);
void test_ShpLoaderDestroy(void);
void test_ShpLoaderGetSQLHeader_drop_prepare(void);
+void test_ShpLoaderGetSQLHeader_if_not_exists_table_modifier(void);
+void test_ShpLoaderGetSQLFooter_if_not_exists_index_modifier(void);
SHPLOADERCONFIG *loader_config;
SHPLOADERSTATE *loader_state;
@@ -37,8 +39,15 @@ CU_pSuite register_shp2pgsql_suite(void)
if ((NULL == CU_add_test(pSuite, "test_ShpLoaderCreate()", test_ShpLoaderCreate)) ||
(NULL == CU_add_test(pSuite, "test_ShpLoaderDestroy()", test_ShpLoaderDestroy)) ||
- (NULL ==
- CU_add_test(pSuite, "test_ShpLoaderGetSQLHeader_drop_prepare()", test_ShpLoaderGetSQLHeader_drop_prepare)))
+ (NULL == CU_add_test(pSuite,
+ "test_ShpLoaderGetSQLHeader_drop_prepare()",
+ test_ShpLoaderGetSQLHeader_drop_prepare)) ||
+ (NULL == CU_add_test(pSuite,
+ "test_ShpLoaderGetSQLHeader_if_not_exists_table_modifier()",
+ test_ShpLoaderGetSQLHeader_if_not_exists_table_modifier)) ||
+ (NULL == CU_add_test(pSuite,
+ "test_ShpLoaderGetSQLFooter_if_not_exists_index_modifier()",
+ test_ShpLoaderGetSQLFooter_if_not_exists_index_modifier)))
{
CU_cleanup_registry();
return NULL;
@@ -87,14 +96,19 @@ test_ShpLoaderGetSQLHeader_drop_prepare(void)
loader_config = (SHPLOADERCONFIG *)calloc(1, sizeof(SHPLOADERCONFIG));
set_loader_config_defaults(loader_config);
- loader_config->opt = 'p';
- loader_config->drop_table = 1;
+ loader_config->actions.mode = 'p';
+ loader_config->actions.drop_table = 1;
/* This header-only test does not open a shapefile, so avoid geometry metadata. */
loader_config->readshape = 0;
loader_config->table = "loadedshp";
loader_config->geo_col = "the_geom";
loader_state = ShpLoaderCreate(loader_config);
+ loader_state->pgtype = "POINT";
+ loader_state->pgdims = 2;
+ loader_state->to_srid = 0;
+ loader_state->num_fields = 0;
+
CU_ASSERT_EQUAL(ShpLoaderGetSQLHeader(loader_state, &header), SHPLOADEROK);
CU_ASSERT_PTR_NOT_NULL(header);
@@ -108,3 +122,56 @@ test_ShpLoaderGetSQLHeader_drop_prepare(void)
free(header);
ShpLoaderDestroy(loader_state);
}
+
+void
+test_ShpLoaderGetSQLHeader_if_not_exists_table_modifier(void)
+{
+ char *header = NULL;
+
+ loader_config = (SHPLOADERCONFIG *)calloc(1, sizeof(SHPLOADERCONFIG));
+ set_loader_config_defaults(loader_config);
+ loader_config->actions.mode = 'p';
+ loader_config->actions.create_table = LOADER_CREATE_IF_NOT_EXISTS;
+ loader_config->actions.create_table_set = 1;
+ loader_config->table = "loadedshp";
+ loader_config->geo_col = "the_geom";
+
+ loader_state = ShpLoaderCreate(loader_config);
+ loader_state->pgtype = "POINT";
+ loader_state->pgdims = 2;
+ loader_state->to_srid = 0;
+ loader_state->num_fields = 0;
+
+ CU_ASSERT_EQUAL(ShpLoaderGetSQLHeader(loader_state, &header), SHPLOADEROK);
+ CU_ASSERT_PTR_NOT_NULL(header);
+ CU_ASSERT_PTR_NOT_NULL(strstr(header,
+ "CREATE TABLE IF NOT EXISTS \"loadedshp\" "
+ "(gid serial PRIMARY KEY,\n"
+ "\"the_geom\" geometry(POINT,0));"));
+ CU_ASSERT_PTR_NULL(strstr(header, "AddGeometryColumn"));
+ CU_ASSERT_PTR_NULL(strstr(header, "ALTER TABLE"));
+
+ free(header);
+ ShpLoaderDestroy(loader_state);
+}
+
+void
+test_ShpLoaderGetSQLFooter_if_not_exists_index_modifier(void)
+{
+ char *footer = NULL;
+
+ loader_config = (SHPLOADERCONFIG *)calloc(1, sizeof(SHPLOADERCONFIG));
+ set_loader_config_defaults(loader_config);
+ loader_config->table = "loadedshp";
+ loader_config->geo_col = "the_geom";
+ loader_config->actions.create_index = LOADER_CREATE_IF_NOT_EXISTS;
+ loader_config->actions.create_index_set = 1;
+
+ loader_state = ShpLoaderCreate(loader_config);
+ CU_ASSERT_EQUAL(ShpLoaderGetSQLFooter(loader_state, &footer), SHPLOADEROK);
+ CU_ASSERT_PTR_NOT_NULL(footer);
+ CU_ASSERT_PTR_NOT_NULL(strstr(footer, "CREATE INDEX IF NOT EXISTS \"loadedshp_the_geom_gist\""));
+
+ free(footer);
+ ShpLoaderDestroy(loader_state);
+}
diff --git a/loader/loader_actions.h b/loader/loader_actions.h
new file mode 100644
index 000000000..ec0c4c095
--- /dev/null
+++ b/loader/loader_actions.h
@@ -0,0 +1,67 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * This is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public Licence. See the COPYING file.
+ *
+ **********************************************************************/
+
+#ifndef LOADER_ACTIONS_H
+#define LOADER_ACTIONS_H 1
+
+#include <stdbool.h>
+
+typedef enum loader_create_mode
+{
+ LOADER_CREATE_NONE = 0,
+ LOADER_CREATE_ALWAYS,
+ LOADER_CREATE_IF_NOT_EXISTS
+} LoaderCreateMode;
+
+typedef enum loader_transaction_mode
+{
+ LOADER_TRANSACTION_OFF = 0,
+ LOADER_TRANSACTION_ON
+} LoaderTransactionMode;
+
+typedef struct loader_plan {
+ int drop_table;
+ LoaderCreateMode create_table;
+ int load_data;
+ LoaderCreateMode create_index;
+ int add_constraints;
+ int vacuum;
+ int analyze;
+ LoaderTransactionMode transaction;
+} LoaderPlan;
+
+typedef struct loader_action_options {
+ char mode;
+ int mode_set;
+ int if_not_exists;
+ int drop_table;
+ LoaderCreateMode create_table;
+ int create_table_set;
+ int load_data;
+ int load_data_set;
+ LoaderCreateMode create_index;
+ int create_index_set;
+ int add_constraints;
+ int vacuum;
+ int analyze;
+} LoaderActionOptions;
+
+static inline bool
+loader_action_set_create_mode(LoaderCreateMode *current_mode, int *is_set, LoaderCreateMode new_mode)
+{
+ if (*is_set && *current_mode != new_mode)
+ return false;
+
+ *current_mode = new_mode;
+ *is_set = true;
+ return true;
+}
+
+#endif /* LOADER_ACTIONS_H */
diff --git a/loader/shp2pgsql-cli.c b/loader/shp2pgsql-cli.c
index 28d1a1c25..bcd03e516 100644
--- a/loader/shp2pgsql-cli.c
+++ b/loader/shp2pgsql-cli.c
@@ -47,6 +47,7 @@ usage()
printf(_( " -k Keep postgresql identifiers case.\n" ));
printf(_( " -i Use int4 type for all integer dbf fields.\n" ));
printf(_( " -I Create a spatial index on the geocolumn.\n" ));
+ printf(_(" --create-index Create a spatial index on the geocolumn.\n"));
printf(_( " -u Create the table as UNLOGGED.\n" ));
printf(_(" -m <filename> Specify a file containing a set of mappings of (long) column\n"
" names to 10 character DBF column names. The content of the file is one or\n"
@@ -63,7 +64,10 @@ usage()
" attribute column. (default: \"UTF-8\")\n" ));
printf(_( " -N <policy> NULL geometries handling policy (insert*,skip,abort).\n" ));
printf(_( " -n Only import DBF file.\n" ));
- printf(_(" --drop-table Emit DROP TABLE IF EXISTS before create or prepare output.\n"));
+ printf(_(" --drop-table Drop the target table before the selected actions.\n"));
+ printf(_(" --create-table Create the target table.\n"));
+ printf(_(" --if-not-exists Make active creation actions use IF NOT EXISTS.\n"));
+ printf(_(" --load-data Load shapefile rows into the target table.\n"));
printf(_( " -T <tablespace> Specify the tablespace for the new table.\n"
" Note that indexes will still use the default tablespace unless the\n"
" -X flag is also used.\n"));
@@ -71,13 +75,15 @@ usage()
" This applies to the primary key, and the spatial index if\n"
" the -I flag is used.\n" ));
printf(_( " -Z Prevent tables from being analyzed.\n" ));
+ printf(_(" --analyze Analyze the table after loading.\n"));
+ printf(_(" --no-analyze Prevent tables from being analyzed.\n"));
+ printf(_(" --no-transaction Execute each statement individually.\n"));
printf(_( " -? Display this help screen.\n" ));
printf( "\n" );
printf(_( " An argument of `--' disables further option processing.\n" ));
printf(_( " (useful for unusual file names starting with '-')\n" ));
}
-
int
main (int argc, char **argv)
{
@@ -109,12 +115,79 @@ main (int argc, char **argv)
/* Keep the flag list alphabetic so it's easy to see what's left. */
while (pgis_optind < argc)
{
+ if (strcmp(argv[pgis_optind], "--") == 0)
+ {
+ pgis_optind++;
+ break;
+ }
+
if (strcmp(argv[pgis_optind], "--drop-table") == 0)
{
- config->drop_table = 1;
+ config->actions.drop_table = 1;
pgis_optind++;
continue;
}
+ if (strcmp(argv[pgis_optind], "--create-table") == 0)
+ {
+ if (!loader_action_set_create_mode(
+ &config->actions.create_table, &config->actions.create_table_set, LOADER_CREATE_ALWAYS))
+ {
+ fprintf(stderr,
+ "Invalid argument combination - conflicting table creation semantics\n");
+ exit(1);
+ }
+ pgis_optind++;
+ continue;
+ }
+ if (strcmp(argv[pgis_optind], "--load-data") == 0)
+ {
+ config->actions.load_data = 1;
+ config->actions.load_data_set = 1;
+ pgis_optind++;
+ continue;
+ }
+ if (strcmp(argv[pgis_optind], "--create-index") == 0)
+ {
+ if (!loader_action_set_create_mode(
+ &config->actions.create_index, &config->actions.create_index_set, LOADER_CREATE_ALWAYS))
+ {
+ fprintf(stderr,
+ "Invalid argument combination - conflicting index creation semantics\n");
+ exit(1);
+ }
+ pgis_optind++;
+ continue;
+ }
+ if (strcmp(argv[pgis_optind], "--if-not-exists") == 0)
+ {
+ config->actions.if_not_exists = 1;
+ pgis_optind++;
+ continue;
+ }
+ if (strcmp(argv[pgis_optind], "--analyze") == 0)
+ {
+ config->analyze = 1;
+ pgis_optind++;
+ continue;
+ }
+ if (strcmp(argv[pgis_optind], "--no-analyze") == 0)
+ {
+ config->analyze = 0;
+ pgis_optind++;
+ continue;
+ }
+ if (strcmp(argv[pgis_optind], "--no-transaction") == 0)
+ {
+ config->usetransaction = 0;
+ pgis_optind++;
+ continue;
+ }
+ if (strncmp(argv[pgis_optind], "--", 2) == 0)
+ {
+ fprintf(stderr, "Unknown option: %s\n", argv[pgis_optind]);
+ usage();
+ exit(1);
+ }
c = pgis_getopt(argc, argv, "-?acdeg:ikm:nps:t:uwDGIN:ST:W:X:Z");
if (c == EOF)
@@ -130,7 +203,8 @@ main (int argc, char **argv)
case 'd':
case 'a':
case 'p':
- config->opt = c;
+ config->actions.mode = c;
+ config->actions.mode_set = 1;
break;
case 'D':
@@ -188,7 +262,13 @@ main (int argc, char **argv)
break;
case 'I':
- config->createindex = 1;
+ if (!loader_action_set_create_mode(
+ &config->actions.create_index, &config->actions.create_index_set, LOADER_CREATE_ALWAYS))
+ {
+ fprintf(stderr,
+ "Invalid argument combination - conflicting index creation semantics\n");
+ exit(1);
+ }
break;
case 'u':
@@ -279,10 +359,27 @@ main (int argc, char **argv)
exit(1);
}
- if (config->drop_table && config->opt == 'a')
{
- fprintf(stderr, "Invalid argument combination - cannot use both --drop-table and -a\n");
- exit(1);
+ const int has_table_creation =
+ config->actions.create_table_set
+ ? config->actions.create_table != LOADER_CREATE_NONE
+ : config->actions.mode != 'a';
+ const int has_index_creation =
+ config->actions.create_index_set && config->actions.create_index == LOADER_CREATE_ALWAYS;
+ const int has_load_data =
+ config->actions.load_data_set ? config->actions.load_data : config->actions.mode != 'p';
+
+ if (config->actions.if_not_exists && !has_table_creation && !has_index_creation)
+ {
+ fprintf(stderr, "--if-not-exists requires an active creation action\n");
+ exit(1);
+ }
+
+ if (config->actions.drop_table && has_load_data && !has_table_creation)
+ {
+ fprintf(stderr, "Invalid argument combination - --drop-table with load data requires --create-table\n");
+ exit(1);
+ }
}
/* Determine the shapefile name from the next argument, if no shape file, exit. */
@@ -404,7 +501,7 @@ main (int argc, char **argv)
free(header);
/* If we are not in "prepare" mode, go ahead and write out the data. */
- if (state->config->load_data)
+ if (state->config->plan.load_data)
{
/* If in COPY mode, output the COPY statement */
diff --git a/loader/shp2pgsql-core.c b/loader/shp2pgsql-core.c
index fa8bffe75..52226802a 100644
--- a/loader/shp2pgsql-core.c
+++ b/loader/shp2pgsql-core.c
@@ -754,6 +754,56 @@ strtolower(char *s)
s[j] = tolower(s[j]);
}
+static void
+pgtype_typmod_name(const SHPLOADERSTATE *state, char *buf, size_t len)
+{
+ const char *suffix = "";
+ size_t base_len = strlen(state->pgtype);
+
+ if (state->pgdims == 4 && state->has_z && state->has_m)
+ suffix = "ZM";
+ else if (state->pgdims == 3 && state->has_z)
+ suffix = "Z";
+ else if (state->pgdims == 3 && state->has_m)
+ suffix = "M";
+
+ if (base_len > 0 && state->pgtype[base_len - 1] == 'M')
+ base_len--;
+
+ snprintf(buf, len, "%.*s%s", (int)base_len, state->pgtype, suffix);
+}
+
+static void
+append_qualified_table(stringbuffer_t *sb, const char *schema, const char *table)
+{
+ if (schema)
+ stringbuffer_aprintf(sb, "\"%s\".", schema);
+ stringbuffer_aprintf(sb, "\"%s\"", table);
+}
+
+static void
+append_primary_key_ddl(const SHPLOADERSTATE *state, stringbuffer_t *sb)
+{
+ stringbuffer_aprintf(sb, "ALTER TABLE ");
+ append_qualified_table(sb, state->config->schema, state->config->table);
+ stringbuffer_aprintf(sb, " ADD PRIMARY KEY (gid);\n");
+
+ if (state->config->idxtablespace != NULL)
+ {
+ stringbuffer_aprintf(sb, "ALTER INDEX ");
+ if (state->config->schema)
+ stringbuffer_aprintf(sb, "\"%s\".", state->config->schema);
+ stringbuffer_aprintf(
+ sb, "\"%s_pkey\" SET TABLESPACE \"%s\";\n", state->config->table, state->config->idxtablespace);
+ }
+}
+
+static int
+plan_has_transactional_work(const LoaderPlan *plan)
+{
+ return plan->create_table != LOADER_CREATE_NONE || plan->load_data ||
+ plan->create_index != LOADER_CREATE_NONE;
+}
/* Default configuration settings */
void
@@ -769,12 +819,13 @@ set_loader_config_defaults(SHPLOADERCONFIG *config)
config->geography = 0;
config->quoteidentifiers = 0;
config->forceint4 = 0;
- config->createindex = 0;
+ config->createindex = LOADER_CREATE_NONE;
config->unlogged = 0;
- config->drop_table = 0;
- config->drop_geometry_column = 0;
- config->create_table = 1;
- config->load_data = 1;
+ memset(&config->actions, 0, sizeof(config->actions));
+ config->actions.mode = 'c';
+ config->actions.create_table = LOADER_CREATE_ALWAYS;
+ config->actions.load_data = 1;
+ memset(&config->plan, 0, sizeof(config->plan));
config->analyze = 1;
config->readshape = 1;
config->force_output = FORCE_OUTPUT_DISABLE;
@@ -792,12 +843,57 @@ set_loader_config_defaults(SHPLOADERCONFIG *config)
static void
set_loader_config_actions(SHPLOADERCONFIG *config)
{
- const int explicit_drop_table = config->drop_table;
+ LoaderActionOptions *actions = &config->actions;
- config->drop_table = explicit_drop_table || config->opt == 'd';
- config->drop_geometry_column = config->opt == 'd';
- config->create_table = config->opt != 'a';
- config->load_data = config->opt != 'p';
+ memset(&config->plan, 0, sizeof(config->plan));
+ config->plan.transaction = config->usetransaction ? LOADER_TRANSACTION_ON : LOADER_TRANSACTION_OFF;
+
+ switch (actions->mode)
+ {
+ case 'd':
+ config->plan.drop_table = 1;
+ config->plan.create_table = LOADER_CREATE_ALWAYS;
+ config->plan.load_data = 1;
+ break;
+ case 'a':
+ config->plan.load_data = 1;
+ break;
+ case 'p':
+ config->plan.create_table = LOADER_CREATE_ALWAYS;
+ break;
+ case 'c':
+ default:
+ config->plan.create_table = LOADER_CREATE_ALWAYS;
+ config->plan.load_data = 1;
+ break;
+ }
+
+ if (actions->drop_table)
+ config->plan.drop_table = 1;
+ if (actions->create_table_set)
+ config->plan.create_table = actions->create_table;
+ if (actions->load_data_set)
+ config->plan.load_data = actions->load_data;
+
+ config->plan.create_index = config->createindex;
+ if (actions->create_index_set)
+ config->plan.create_index = actions->create_index;
+
+ if (actions->if_not_exists)
+ {
+ if (config->plan.create_table == LOADER_CREATE_ALWAYS)
+ config->plan.create_table = LOADER_CREATE_IF_NOT_EXISTS;
+ if (config->plan.create_index == LOADER_CREATE_ALWAYS)
+ config->plan.create_index = LOADER_CREATE_IF_NOT_EXISTS;
+ }
+
+ if (actions->analyze)
+ config->plan.analyze = 1;
+ else if (config->analyze && (config->plan.create_table != LOADER_CREATE_NONE || config->plan.load_data ||
+ config->plan.create_index != LOADER_CREATE_NONE))
+ {
+ config->plan.analyze = 1;
+ }
}
/* Create a new shapefile state object */
@@ -1301,6 +1397,8 @@ ShpLoaderGetSQLHeader(SHPLOADERSTATE *state, char **strheader)
stringbuffer_t *sb;
char *ret;
int j;
+ const int use_transaction =
+ state->config->plan.transaction == LOADER_TRANSACTION_ON && plan_has_transactional_work(&state->config->plan);
/* Create the stringbuffer containing the header; we use this API as it's easier
for handling string resizing during append */
@@ -1317,67 +1415,44 @@ ShpLoaderGetSQLHeader(SHPLOADERSTATE *state, char **strheader)
stringbuffer_aprintf(sb, "SET STANDARD_CONFORMING_STRINGS TO ON;\n");
/* Drop table if requested */
- if (state->config->drop_table)
+ if (state->config->plan.drop_table)
{
- /**
- * TODO: if the table has more then one geometry column
- * the DROP TABLE call will leave spurious records in
- * geometry_columns.
- *
- * If the geometry column in the table being dropped
- * does not match 'the_geom' or the name specified with
- * -g an error is returned by DropGeometryColumn.
- *
- * The table to be dropped might not exist.
- */
if (state->config->schema)
{
- if (state->config->drop_geometry_column && state->config->readshape == 1 &&
- (!state->config->geography))
- {
- stringbuffer_aprintf(sb, "SELECT DropGeometryColumn('%s','%s','%s');\n",
- state->config->schema, state->config->table, state->geo_col);
- }
-
stringbuffer_aprintf(sb, "DROP TABLE IF EXISTS \"%s\".\"%s\";\n", state->config->schema,
state->config->table);
}
else
{
- if (state->config->drop_geometry_column && state->config->readshape == 1 &&
- (!state->config->geography))
- {
- stringbuffer_aprintf(sb, "SELECT DropGeometryColumn('','%s','%s');\n",
- state->config->table, state->geo_col);
- }
-
stringbuffer_aprintf(sb, "DROP TABLE IF EXISTS \"%s\";\n", state->config->table);
}
}
/* Start of transaction if we are using one */
- if (state->config->usetransaction)
+ if (use_transaction)
{
stringbuffer_aprintf(sb, "BEGIN;\n");
}
/* Create the spatial table when requested by the selected actions. */
- if (state->config->create_table)
+ if (state->config->plan.create_table != LOADER_CREATE_NONE)
{
+ const int if_not_exists = state->config->plan.create_table == LOADER_CREATE_IF_NOT_EXISTS;
+
/*
- * Create a table for inserting the shapes into with appropriate
- * columns and types
- */
- if (state->config->schema)
+ * Create a table for inserting the shapes into with appropriate
+ * columns and types
+ */
+ stringbuffer_aprintf(sb,
+ "CREATE %sTABLE %s",
+ state->config->unlogged ? "UNLOGGED " : "",
+ if_not_exists ? "IF NOT EXISTS " : "");
+ append_qualified_table(sb, state->config->schema, state->config->table);
+ stringbuffer_aprintf(sb, " (gid serial%s", if_not_exists ? " PRIMARY KEY" : "");
+
+ if (if_not_exists && state->config->idxtablespace != NULL)
{
- stringbuffer_aprintf(sb, "CREATE %sTABLE \"%s\".\"%s\" (gid serial",
- state->config->unlogged ? "UNLOGGED " : "",
- state->config->schema, state->config->table);
- }
- else
- {
- stringbuffer_aprintf(sb, "CREATE %sTABLE \"%s\" (gid serial",
- state->config->unlogged ? "UNLOGGED " : "", state->config->table);
+ stringbuffer_aprintf(sb, " USING INDEX TABLESPACE \"%s\"", state->config->idxtablespace);
}
/* Generate the field types based upon the shapefile information */
@@ -1401,22 +1476,22 @@ ShpLoaderGetSQLHeader(SHPLOADERSTATE *state, char **strheader)
}
}
- /* Add the geography column directly to the table definition, we don't
- need to do an AddGeometryColumn() call. */
- if (state->config->readshape == 1 && state->config->geography)
+ /*
+ * geometry_columns is a view in current PostGIS, so typmod column
+ * DDL is equivalent to the default AddGeometryColumn path.
+ */
+ if (state->config->readshape == 1)
{
- char *dimschar;
-
- if (state->pgdims == 4)
- dimschar = "ZM";
- else
- dimschar = "";
-
- if (state->to_srid == SRID_UNKNOWN ){
+ const char *coltype = state->config->geography ? "geography" : "geometry";
+ char typmod_type[64];
+ if (state->config->geography && state->to_srid == SRID_UNKNOWN)
+ {
state->to_srid = 4326;
}
- stringbuffer_aprintf(sb, ",\n\"%s\" geography(%s%s,%d)", state->geo_col, state->pgtype, dimschar, state->to_srid);
+ pgtype_typmod_name(state, typmod_type, sizeof(typmod_type));
+ stringbuffer_aprintf(
+ sb, ",\n\"%s\" %s(%s,%d)", state->geo_col, coltype, typmod_type, state->to_srid);
}
stringbuffer_aprintf(sb, ")");
@@ -1427,61 +1502,8 @@ ShpLoaderGetSQLHeader(SHPLOADERSTATE *state, char **strheader)
}
stringbuffer_aprintf(sb, ";\n");
- /* Create the primary key. This is done separately because the index for the PK needs
- * to be in the correct tablespace. */
-
- /* TODO: Currently PostgreSQL does not allow specifying an index to use for a PK (so you get
- * a default one called table_pkey) and it does not provide a way to create a PK index
- * in a specific tablespace. So as a hacky solution we create the PK, then move the
- * index to the correct tablespace. Eventually this should be:
- * CREATE INDEX table_pkey on table(gid) TABLESPACE tblspc;
- * ALTER TABLE table ADD PRIMARY KEY (gid) USING INDEX table_pkey;
- * A patch has apparently been submitted to PostgreSQL to enable this syntax, see this thread:
- * http://archives.postgresql.org/pgsql-hackers/2011-01/msg01405.php */
- stringbuffer_aprintf(sb, "ALTER TABLE ");
-
- /* Schema is optional, include if present. */
- if (state->config->schema)
- {
- stringbuffer_aprintf(sb, "\"%s\".",state->config->schema);
- }
- stringbuffer_aprintf(sb, "\"%s\" ADD PRIMARY KEY (gid);\n", state->config->table);
-
- /* Tablespace is optional for the index. */
- if (state->config->idxtablespace != NULL)
- {
- stringbuffer_aprintf(sb, "ALTER INDEX ");
- if (state->config->schema)
- {
- stringbuffer_aprintf(sb, "\"%s\".",state->config->schema);
- }
-
- /* WARNING: We're assuming the default "table_pkey" name for the primary
- * key index. PostgreSQL may use "table_pkey1" or similar in the
- * case of a name conflict, so you may need to edit the produced
- * SQL in this rare case. */
- stringbuffer_aprintf(sb, "\"%s_pkey\" SET TABLESPACE \"%s\";\n",
- state->config->table, state->config->idxtablespace);
- }
-
- /* Create the geometry column with an addgeometry call */
- if (state->config->readshape == 1 && (!state->config->geography))
- {
- /* If they didn't specify a target SRID, see if they specified a source SRID. */
- int32_t srid = state->to_srid;
- if (state->config->schema)
- {
- stringbuffer_aprintf(sb, "SELECT AddGeometryColumn('%s','%s','%s','%d',",
- state->config->schema, state->config->table, state->geo_col, srid);
- }
- else
- {
- stringbuffer_aprintf(sb, "SELECT AddGeometryColumn('','%s','%s','%d',",
- state->config->table, state->geo_col, srid);
- }
-
- stringbuffer_aprintf(sb, "'%s',%d);\n", state->pgtype, state->pgdims);
- }
+ if (!if_not_exists)
+ append_primary_key_ddl(state, sb);
}
/**If we are in dump mode and a transform was asked for need to create a temp table to store original data
@@ -1900,6 +1922,8 @@ ShpLoaderGetSQLFooter(SHPLOADERSTATE *state, char **strfooter)
{
stringbuffer_t *sb;
char *ret;
+ const int use_transaction =
+ state->config->plan.transaction == LOADER_TRANSACTION_ON && plan_has_transactional_work(&state->config->plan);
/* Create the stringbuffer containing the header; we use this API as it's easier
for handling string resizing during append */
@@ -1926,9 +1950,14 @@ ShpLoaderGetSQLFooter(SHPLOADERSTATE *state, char **strfooter)
}
/* Create gist index if specified and not in "prepare" mode */
- if (state->config->readshape && state->config->createindex)
+ if (state->config->readshape && state->config->plan.create_index != LOADER_CREATE_NONE)
{
- stringbuffer_aprintf(sb, "CREATE INDEX ON ");
+ stringbuffer_aprintf(sb, "CREATE INDEX ");
+ if (state->config->plan.create_index == LOADER_CREATE_IF_NOT_EXISTS)
+ {
+ stringbuffer_aprintf(sb, "IF NOT EXISTS \"%s_%s_gist\" ", state->config->table, state->geo_col);
+ }
+ stringbuffer_aprintf(sb, "ON ");
/* Schema is optional, include if present. */
if (state->config->schema)
{
@@ -1944,13 +1973,12 @@ ShpLoaderGetSQLFooter(SHPLOADERSTATE *state, char **strfooter)
}
/* End the transaction if there is one. */
- if (state->config->usetransaction)
+ if (use_transaction)
{
stringbuffer_aprintf(sb, "COMMIT;\n");
}
-
- if(state->config->analyze)
+ if (state->config->plan.analyze)
{
/* Always ANALYZE the resulting table, for better stats */
stringbuffer_aprintf(sb, "ANALYZE ");
diff --git a/loader/shp2pgsql-core.h b/loader/shp2pgsql-core.h
index ed8e5ae71..b106e2cde 100644
--- a/loader/shp2pgsql-core.h
+++ b/loader/shp2pgsql-core.h
@@ -26,6 +26,7 @@
#include "shapefil.h"
#include "shpcommon.h"
#include "getopt.h"
+#include "loader_actions.h"
#include "../liblwgeom/stringbuffer.h"
@@ -111,23 +112,15 @@ typedef struct shp_loader_config
/* 0 = allow int8 fields, 1 = no int8 fields */
int forceint4;
- /* 0 = no index, 1 = create index after load */
- int createindex;
+ /* index creation action */
+ LoaderCreateMode createindex;
/* 0 = logged table, 1 = create as UNLOGGED table */
int unlogged;
- /* action: emit DROP TABLE before create/prepare */
- int drop_table;
-
- /* action: remove geometry_columns entry before dropping the table */
- int drop_geometry_column;
-
- /* action: create the target table */
- int create_table;
-
- /* action: load rows from the input file */
- int load_data;
+ /* raw action options and normalized execution plan */
+ LoaderActionOptions actions;
+ LoaderPlan plan;
/* 0 = don't analyze tables , 1 = analyze tables */
int analyze;
diff --git a/loader/shp2pgsql-gui.c b/loader/shp2pgsql-gui.c
index 853a8c3af..51307ccba 100644
--- a/loader/shp2pgsql-gui.c
+++ b/loader/shp2pgsql-gui.c
@@ -587,9 +587,9 @@ update_loader_config_globals_from_options_ui(SHPLOADERCONFIG *config)
/* Create spatial index after load */
if (createindex)
- config->createindex = 1;
+ config->createindex = LOADER_CREATE_ALWAYS;
else
- config->createindex = 0;
+ config->createindex = LOADER_CREATE_NONE;
/* Read the .shp file, don't ignore it */
if (dbfonly)
@@ -597,7 +597,7 @@ update_loader_config_globals_from_options_ui(SHPLOADERCONFIG *config)
config->readshape = 0;
/* There will be no spatial column so don't create a spatial index */
- config->createindex = 0;
+ config->createindex = LOADER_CREATE_NONE;
}
else
config->readshape = 1;
@@ -1188,7 +1188,7 @@ validate_remote_loader_columns(SHPLOADERCONFIG *config, PGresult *result)
{
ntuples = PQntuples(result);
- switch (config->opt)
+ switch (config->actions.mode)
{
case 'c':
/* If we have a row matching the table given in the config, then it already exists */
@@ -1540,7 +1540,19 @@ pgui_action_import(GtkWidget *widget, gpointer data)
loader_file_config = (SHPLOADERCONFIG *)gptr;
pgui_logf("\n==============================");
- pgui_logf("Importing with configuration: %s, %s, %s, %s, mode=%c, dump=%d, simple=%d, geography=%d, index=%d, shape=%d, srid=%d", loader_file_config->table, loader_file_config->schema, loader_file_config->geo_col, loader_file_config->shp_file, loader_file_config->opt, loader_file_config->dump_format, loader_file_config->simple_geometries, loader_file_config->geography, loader_file_config->createindex, loader_file_config->readshape, loader_file_config->sr_id);
+ pgui_logf(
+ "Importing with configuration: %s, %s, %s, %s, mode=%c, dump=%d, simple=%d, geography=%d, index=%d, shape=%d, srid=%d",
+ loader_file_config->table,
+ loader_file_config->schema,
+ loader_file_config->geo_col,
+ loader_file_config->shp_file,
+ loader_file_config->actions.mode,
+ loader_file_config->dump_format,
+ loader_file_config->simple_geometries,
+ loader_file_config->geography,
+ loader_file_config->createindex,
+ loader_file_config->readshape,
+ loader_file_config->sr_id);
/*
* Loop through the items in the shapefile
@@ -1610,7 +1622,7 @@ pgui_action_import(GtkWidget *widget, gpointer data)
goto import_cleanup;
/* Load rows when requested by the selected actions. */
- if (state->config->load_data)
+ if (state->config->plan.load_data)
{
int numrecords = ShpLoaderGetRecordCount(state);
int records_per_tick = (numrecords / 200) - 1;
@@ -1714,7 +1726,7 @@ pgui_action_import(GtkWidget *widget, gpointer data)
goto import_cleanup;
}
}
- } /* if (state->config->load_data) */
+ } /* if (state->config->plan.load_data) */
/* Only continue if we didn't abort part way through */
if (is_running)
@@ -1730,7 +1742,7 @@ pgui_action_import(GtkWidget *widget, gpointer data)
}
/* Just in case index creation takes a long time, update the progress text */
- if (state->config->createindex)
+ if (state->config->plan.create_index != LOADER_CREATE_NONE)
{
gtk_label_set_text(GTK_LABEL(label_progress), _("Creating spatial index..."));
@@ -1756,7 +1768,7 @@ import_cleanup:
pg_connection = NULL;
/* If we didn't finish inserting all of the items (and we expected to), an error occurred */
- if ((state->config->load_data && i != ShpLoaderGetRecordCount(state)) || !ret)
+ if ((state->config->plan.load_data && i != ShpLoaderGetRecordCount(state)) || !ret)
pgui_logf(_("Shapefile import failed."));
else
pgui_logf(_("Shapefile import completed."));
@@ -2173,23 +2185,23 @@ pgui_action_handle_tree_combo(GtkCellRendererCombo *combo,
switch (opt)
{
case 'a':
- loader_file_config->opt = 'a';
+ loader_file_config->actions.mode = 'a';
/* Other half of index creation hack */
- loader_file_config->createindex = 0;
+ loader_file_config->createindex = LOADER_CREATE_NONE;
break;
case 'd':
- loader_file_config->opt = 'd';
+ loader_file_config->actions.mode = 'd';
break;
case 'p':
- loader_file_config->opt = 'p';
+ loader_file_config->actions.mode = 'p';
break;
case 'c':
- loader_file_config->opt = 'c';
+ loader_file_config->actions.mode = 'c';
break;
}
@@ -3518,7 +3530,7 @@ main(int argc, char *argv[])
set_dumper_config_defaults(global_dumper_config);
/* Here we override any defaults for the GUI */
- global_loader_config->createindex = 1;
+ global_loader_config->createindex = LOADER_CREATE_ALWAYS;
global_loader_config->geo_col = strdup(GEOMETRY_DEFAULT);
global_loader_config->dump_format = 1;
diff --git a/raster/loader/Makefile.in b/raster/loader/Makefile.in
index a08bbc355..6b4babaeb 100644
--- a/raster/loader/Makefile.in
+++ b/raster/loader/Makefile.in
@@ -84,7 +84,7 @@ LDFLAGS = \
all: $(RASTER2PGSQL)
-raster2pgsql.o: raster2pgsql.c
+raster2pgsql.o: raster2pgsql.c ../../loader/loader_actions.h
$(CC) $(CPPFLAGS) $(CFLAGS) -c $<
$(RASTER2PGSQL): ../rt_core/librtcore.a raster2pgsql.o
diff --git a/raster/loader/raster2pgsql.c b/raster/loader/raster2pgsql.c
index 85508bb67..0a7474945 100644
--- a/raster/loader/raster2pgsql.c
+++ b/raster/loader/raster2pgsql.c
@@ -382,7 +382,14 @@ usage() {
" -p Prepare mode, only creates the table.\n"));
printf(
_(" --if-not-exists Use IF NOT EXISTS for table creation in -c and -p\n"
- " modes. With -I, also use IF NOT EXISTS for index creation.\n"));
+ " modes. With -I, also use IF NOT EXISTS for index creation.\n"
+ " Append mode requires an explicit creation action.\n"));
+ printf(
+ _(" --drop-table Drop the target table before other actions.\n"
+ " With no mode specified, the default create/load actions still apply.\n"
+ " --create-table Create the target table.\n"
+ " --load-data Load raster data into the target table.\n"
+ " --create-index Create a GIST spatial index on the raster column.\n"));
printf(_(
" -f <column> Specify the name of the raster column\n"
));
@@ -405,6 +412,12 @@ usage() {
" -I Create a GIST spatial index on the raster column. The ANALYZE\n"
" command will automatically be issued for the created index.\n"
));
+ printf(
+ _(" --add-constraints Set the standard set of constraints on the\n"
+ " raster column after the rasters are loaded.\n"
+ " --vacuum Run VACUUM on the table of the raster column.\n"
+ " --analyze Run ANALYZE on the table of the raster column.\n"
+ " --no-transaction Execute statements without a transaction.\n"));
printf(_(
" -M Run VACUUM ANALYZE on the table of the raster column. Most\n"
" useful when appending raster to existing table with -a.\n"
@@ -692,13 +705,11 @@ init_config(RTLOADERCFG *config) {
config->pad_tile = 0;
config->outdb = 0;
config->opt = 'c';
- config->if_not_exists = 0;
- config->drop_table = 0;
- config->create_table = CREATE_TABLE_ALWAYS;
- config->load_data = 1;
- config->create_index = CREATE_INDEX_NONE;
- config->maintenance = 0;
- config->constraints = 0;
+ memset(&config->actions, 0, sizeof(config->actions));
+ config->actions.mode = 'c';
+ config->actions.create_table = LOADER_CREATE_ALWAYS;
+ config->actions.load_data = 1;
+ memset(&config->plan, 0, sizeof(config->plan));
config->max_extent = 1;
config->regular_blocking = 0;
config->tablespace = NULL;
@@ -713,47 +724,75 @@ init_config(RTLOADERCFG *config) {
config->max_tiles_per_copy = 50;
}
+static void rtdealloc_config(RTLOADERCFG *config);
+
+static void
+exit_config_error(RTLOADERCFG *config)
+{
+ rtdealloc_config(config);
+ exit(1);
+}
+
static int
apply_action_presets(RTLOADERCFG *config)
{
- config->drop_table = 0;
- config->create_table = CREATE_TABLE_NONE;
- config->load_data = 0;
+ LoaderActionOptions *actions = &config->actions;
- switch (config->opt)
+ memset(&config->plan, 0, sizeof(config->plan));
+
+ switch (actions->mode)
{
case 'd':
- config->drop_table = 1;
- config->create_table = CREATE_TABLE_ALWAYS;
- config->load_data = 1;
+ config->plan.drop_table = 1;
+ config->plan.create_table = LOADER_CREATE_ALWAYS;
+ config->plan.load_data = 1;
break;
case 'a':
- config->load_data = 1;
+ config->plan.load_data = 1;
break;
case 'c':
- config->create_table = CREATE_TABLE_ALWAYS;
- config->load_data = 1;
+ config->plan.create_table = LOADER_CREATE_ALWAYS;
+ config->plan.load_data = 1;
break;
case 'p':
- config->create_table = CREATE_TABLE_ALWAYS;
+ config->plan.create_table = LOADER_CREATE_ALWAYS;
break;
default:
- rterror(_("Unknown loader operation: -%c"), config->opt);
+ rterror(_("Unknown loader operation: -%c"), actions->mode);
return 0;
}
- if (config->if_not_exists)
+ if (actions->drop_table)
+ config->plan.drop_table = 1;
+ if (actions->create_table_set)
+ config->plan.create_table = actions->create_table;
+ if (actions->load_data_set)
+ config->plan.load_data = actions->load_data;
+ if (actions->create_index_set)
+ config->plan.create_index = actions->create_index;
+ if (actions->add_constraints)
+ config->plan.add_constraints = 1;
+ config->plan.vacuum = actions->vacuum;
+ config->plan.analyze = actions->analyze;
+
+ if (config->plan.drop_table && config->plan.load_data && config->plan.create_table == LOADER_CREATE_NONE)
{
- if (config->opt == 'd' || config->opt == 'a')
+ rterror(_("--drop-table with load data requires a table creation action"));
+ return 0;
+ }
+
+ if (actions->if_not_exists)
+ {
+ if (config->plan.create_table == LOADER_CREATE_NONE && config->plan.create_index == LOADER_CREATE_NONE)
{
- rterror(_("--if-not-exists can only modify create and prepare modes"));
+ rterror(_("--if-not-exists requires a table or index creation action"));
return 0;
}
- if (config->create_table == CREATE_TABLE_ALWAYS)
- config->create_table = CREATE_TABLE_IF_NOT_EXISTS;
- if (config->create_index == CREATE_INDEX_ALWAYS)
- config->create_index = CREATE_INDEX_IF_NOT_EXISTS;
+ if (config->plan.create_table == LOADER_CREATE_ALWAYS)
+ config->plan.create_table = LOADER_CREATE_IF_NOT_EXISTS;
+ if (config->plan.create_index == LOADER_CREATE_ALWAYS)
+ config->plan.create_index = LOADER_CREATE_IF_NOT_EXISTS;
}
return 1;
@@ -1202,10 +1241,8 @@ analyze_table(
}
static int
-vacuum_table(
- const char *schema, const char *table,
- STRINGBUFFER *buffer
-) {
+vacuum_table(const char *schema, const char *table, int analyze, STRINGBUFFER *buffer)
+{
char *sql = NULL;
uint32_t len = 0;
@@ -1221,10 +1258,7 @@ vacuum_table(
rterror(_("vacuum_table: Could not allocate memory for VACUUM statement"));
return 0;
}
- sprintf(sql, "VACUUM ANALYZE %s%s;",
- (schema != NULL ? schema : ""),
- table
- );
+ sprintf(sql, "VACUUM%s %s%s;", (analyze ? " ANALYZE" : ""), (schema != NULL ? schema : ""), table);
append_sql_to_buffer(buffer, sql);
@@ -2080,7 +2114,7 @@ process_rasters(RTLOADERCFG *config, STRINGBUFFER *buffer) {
}
/* drop table */
- if (config->drop_table)
+ if (config->plan.drop_table)
{
if (!drop_table(config->schema, config->table, buffer)) {
rterror(_("process_rasters: Could not add DROP TABLE statement to string buffer"));
@@ -2098,7 +2132,7 @@ process_rasters(RTLOADERCFG *config, STRINGBUFFER *buffer) {
}
/* create table */
- if (config->create_table != CREATE_TABLE_NONE)
+ if (config->plan.create_table != LOADER_CREATE_NONE)
{
if (!create_table(config->schema,
config->table,
@@ -2107,7 +2141,7 @@ process_rasters(RTLOADERCFG *config, STRINGBUFFER *buffer) {
config->file_column_name,
config->tablespace,
config->idx_tablespace,
- config->create_table == CREATE_TABLE_IF_NOT_EXISTS,
+ config->plan.create_table == LOADER_CREATE_IF_NOT_EXISTS,
buffer))
{
rterror(_("process_rasters: Could not add CREATE TABLE statement to string buffer"));
@@ -2123,7 +2157,7 @@ process_rasters(RTLOADERCFG *config, STRINGBUFFER *buffer) {
config->file_column_name,
config->tablespace,
config->idx_tablespace,
- config->create_table == CREATE_TABLE_IF_NOT_EXISTS,
+ config->plan.create_table == LOADER_CREATE_IF_NOT_EXISTS,
buffer))
{
rterror(_("process_rasters: Could not add an overview's CREATE TABLE statement to string buffer"));
@@ -2134,7 +2168,7 @@ process_rasters(RTLOADERCFG *config, STRINGBUFFER *buffer) {
}
/* no need to load data in prepare mode */
- if (config->load_data)
+ if (config->plan.load_data)
{
RASTERINFO refinfo;
init_rastinfo(&refinfo);
@@ -2223,14 +2257,14 @@ process_rasters(RTLOADERCFG *config, STRINGBUFFER *buffer) {
}
/* index */
- if (config->create_index != CREATE_INDEX_NONE)
+ if (config->plan.create_index != LOADER_CREATE_NONE)
{
/* create index */
if (!create_index(config->schema,
config->table,
config->raster_column,
config->idx_tablespace,
- config->create_index == CREATE_INDEX_IF_NOT_EXISTS,
+ config->plan.create_index == LOADER_CREATE_IF_NOT_EXISTS,
buffer))
{
rterror(_("process_rasters: Could not add CREATE INDEX statement to string buffer"));
@@ -2238,7 +2272,7 @@ process_rasters(RTLOADERCFG *config, STRINGBUFFER *buffer) {
}
/* analyze */
- if (config->load_data)
+ if (config->plan.load_data)
{
if (!analyze_table(
config->schema, config->table,
@@ -2256,7 +2290,7 @@ process_rasters(RTLOADERCFG *config, STRINGBUFFER *buffer) {
config->overview_table[i],
config->raster_column,
config->idx_tablespace,
- config->create_index == CREATE_INDEX_IF_NOT_EXISTS,
+ config->plan.create_index == LOADER_CREATE_IF_NOT_EXISTS,
buffer))
{
rterror(_("process_rasters: Could not add an overview's CREATE INDEX statement to string buffer"));
@@ -2264,7 +2298,7 @@ process_rasters(RTLOADERCFG *config, STRINGBUFFER *buffer) {
}
/* analyze */
- if (config->load_data)
+ if (config->plan.load_data)
{
if (!analyze_table(
config->schema, config->overview_table[i],
@@ -2279,7 +2313,8 @@ process_rasters(RTLOADERCFG *config, STRINGBUFFER *buffer) {
}
/* add constraints */
- if (config->constraints) {
+ if (config->plan.add_constraints)
+ {
if (!add_raster_constraints(
config->schema, config->table, config->raster_column,
config->regular_blocking, config->max_extent,
@@ -2326,12 +2361,10 @@ process_rasters(RTLOADERCFG *config, STRINGBUFFER *buffer) {
}
/* maintenance */
- if (config->load_data && config->maintenance)
+ if (config->plan.vacuum)
{
- if (!vacuum_table(
- config->schema, config->table,
- buffer
- )) {
+ if (!vacuum_table(config->schema, config->table, config->plan.analyze, buffer))
+ {
rterror(_("process_rasters: Could not add VACUUM statement to string buffer"));
return 0;
}
@@ -2339,15 +2372,35 @@ process_rasters(RTLOADERCFG *config, STRINGBUFFER *buffer) {
if (config->overview_count) {
for (i = 0; i < config->overview_count; i++) {
if (!vacuum_table(
- config->schema, config->overview_table[i],
- buffer
- )) {
+ config->schema, config->overview_table[i], config->plan.analyze, buffer))
+ {
rterror(_("process_rasters: Could not add an overview's VACUUM statement to string buffer"));
return 0;
}
}
}
}
+ else if (config->plan.analyze)
+ {
+ if (!analyze_table(config->schema, config->table, buffer))
+ {
+ rterror(_("process_rasters: Could not add ANALYZE statement to string buffer"));
+ return 0;
+ }
+
+ if (config->overview_count)
+ {
+ for (i = 0; i < config->overview_count; i++)
+ {
+ if (!analyze_table(config->schema, config->overview_table[i], buffer))
+ {
+ rterror(_(
+ "process_rasters: Could not add an overview's ANALYZE statement to string buffer"));
+ return 0;
+ }
+ }
+ }
+ }
return 1;
}
@@ -2542,24 +2595,46 @@ main(int argc, char **argv) {
}
/* drop table and recreate */
else if (CSEQUAL(argv[argit], "-d")) {
- config->opt = 'd';
+ config->actions.mode = 'd';
+ config->actions.mode_set = 1;
}
/* append to table */
else if (CSEQUAL(argv[argit], "-a")) {
- config->opt = 'a';
+ config->actions.mode = 'a';
+ config->actions.mode_set = 1;
}
/* create new table */
else if (CSEQUAL(argv[argit], "-c")) {
- config->opt = 'c';
+ config->actions.mode = 'c';
+ config->actions.mode_set = 1;
}
/* prepare only */
else if (CSEQUAL(argv[argit], "-p")) {
- config->opt = 'p';
+ config->actions.mode = 'p';
+ config->actions.mode_set = 1;
}
/* make creation statements idempotent */
else if (CSEQUAL(argv[argit], "--if-not-exists"))
{
- config->if_not_exists = 1;
+ config->actions.if_not_exists = 1;
+ }
+ else if (CSEQUAL(argv[argit], "--drop-table"))
+ {
+ config->actions.drop_table = 1;
+ }
+ else if (CSEQUAL(argv[argit], "--create-table"))
+ {
+ if (!loader_action_set_create_mode(
+ &config->actions.create_table, &config->actions.create_table_set, LOADER_CREATE_ALWAYS))
+ {
+ rterror(_("--create-table was specified with conflicting creation semantics"));
+ exit_config_error(config);
+ }
+ }
+ else if (CSEQUAL(argv[argit], "--load-data"))
+ {
+ config->actions.load_data = 1;
+ config->actions.load_data_set = 1;
}
/* raster column name */
else if (CSEQUAL(argv[argit], "-f") && argit < argc - 1) {
@@ -2628,15 +2703,42 @@ main(int argc, char **argv) {
}
/* create index */
else if (CSEQUAL(argv[argit], "-I")) {
- config->create_index = CREATE_INDEX_ALWAYS;
+ if (!loader_action_set_create_mode(
+ &config->actions.create_index, &config->actions.create_index_set, LOADER_CREATE_ALWAYS))
+ {
+ rterror(_("--create-index was specified with conflicting creation semantics"));
+ exit_config_error(config);
+ }
+ }
+ else if (CSEQUAL(argv[argit], "--create-index"))
+ {
+ if (!loader_action_set_create_mode(
+ &config->actions.create_index, &config->actions.create_index_set, LOADER_CREATE_ALWAYS))
+ {
+ rterror(_("--create-index was specified with conflicting creation semantics"));
+ exit_config_error(config);
+ }
}
/* maintenance */
else if (CSEQUAL(argv[argit], "-M")) {
- config->maintenance = 1;
+ config->actions.vacuum = 1;
+ config->actions.analyze = 1;
+ }
+ else if (CSEQUAL(argv[argit], "--vacuum"))
+ {
+ config->actions.vacuum = 1;
+ }
+ else if (CSEQUAL(argv[argit], "--analyze"))
+ {
+ config->actions.analyze = 1;
}
/* set constraints */
else if (CSEQUAL(argv[argit], "-C")) {
- config->constraints = 1;
+ config->actions.add_constraints = 1;
+ }
+ else if (CSEQUAL(argv[argit], "--add-constraints"))
+ {
+ config->actions.add_constraints = 1;
}
/* disable extent constraint */
else if (CSEQUAL(argv[argit], "-x")) {
@@ -2691,6 +2793,16 @@ main(int argc, char **argv) {
else if (CSEQUAL(argv[argit], "-e")) {
config->transaction = 0;
}
+ else if (CSEQUAL(argv[argit], "--no-transaction"))
+ {
+ config->transaction = 0;
+ }
+ else if (CSEQUAL(argv[argit], "--transaction"))
+ {
+ rterror(_("--transaction is the default; omit -e/--no-transaction instead"));
+ rtdealloc_config(config);
+ exit(1);
+ }
/* COPY statements */
else if (CSEQUAL(argv[argit], "-Y")) {
config->copy_statements = 1;
diff --git a/raster/loader/raster2pgsql.h b/raster/loader/raster2pgsql.h
index 96f674191..a18e76ea8 100644
--- a/raster/loader/raster2pgsql.h
+++ b/raster/loader/raster2pgsql.h
@@ -44,6 +44,7 @@
#include "../../postgis_config.h"
#include "../raster_config.h"
+#include "../../loader/loader_actions.h"
#define CSEQUAL(a,b) (strcmp(a,b)==0)
@@ -69,20 +70,6 @@
#define RCSID "$Id$"
-typedef enum raster_loader_create_table_action
-{
- CREATE_TABLE_NONE = 0,
- CREATE_TABLE_ALWAYS,
- CREATE_TABLE_IF_NOT_EXISTS
-} CREATE_TABLE_ACTION;
-
-typedef enum raster_loader_create_index_action
-{
- CREATE_INDEX_NONE = 0,
- CREATE_INDEX_ALWAYS,
- CREATE_INDEX_IF_NOT_EXISTS
-} CREATE_INDEX_ACTION;
-
typedef struct raster_loader_config {
/* raster filename */
uint32_t rt_file_count;
@@ -132,20 +119,9 @@ typedef struct raster_loader_config {
/* type of operation, (d|a|c|p) */
char opt;
- /* make creation actions idempotent */
- int if_not_exists;
-
- /* actions derived from operation presets */
- int drop_table;
- CREATE_TABLE_ACTION create_table;
- int load_data;
- CREATE_INDEX_ACTION create_index;
-
- /* maintenance statements, 1 = yes, 0 = no (default) */
- int maintenance;
-
- /* set constraints */
- int constraints;
+ /* raw action options and normalized execution plan */
+ LoaderActionOptions actions;
+ LoaderPlan plan;
/* enable max extent constraint, 1 = yes (default), 0 = no */
int max_extent;
diff --git a/raster/test/regress/loader/LongMaintenance.opts b/raster/test/regress/loader/LongMaintenance.opts
new file mode 100644
index 000000000..0320409c5
--- /dev/null
+++ b/raster/test/regress/loader/LongMaintenance.opts
@@ -0,0 +1,2 @@
+# Exercise raster-only long maintenance actions and constraint creation.
+--add-constraints --vacuum --analyze
diff --git a/raster/test/regress/loader/LongMaintenance.select.expected b/raster/test/regress/loader/LongMaintenance.select.expected
new file mode 100644
index 000000000..484721c35
--- /dev/null
+++ b/raster/test/regress/loader/LongMaintenance.select.expected
@@ -0,0 +1,4 @@
+0|1.0000000000|-1.0000000000|90|50|t|f|3|{8BUI,8BUI,8BUI}|{NULL,NULL,NULL}|{f,f,f}|POLYGON((0 -50,0 0,90 0,90 -50,0 -50))
+POLYGON((0 0,1 0,1 -1,0 -1,0 0))|255
+POLYGON((89 -49,90 -49,90 -50,89 -50,89 -49))|0
+POLYGON((44 -24,45 -24,45 -25,44 -25,44 -24))|0
diff --git a/raster/test/regress/loader/LongMaintenance.select.sql b/raster/test/regress/loader/LongMaintenance.select.sql
new file mode 100644
index 000000000..18027ebe8
--- /dev/null
+++ b/raster/test/regress/loader/LongMaintenance.select.sql
@@ -0,0 +1,8 @@
+-- --add-constraints should register the loaded raster in raster_columns
+-- with the same metadata that the existing short -C test expects.
+SELECT srid, scale_x::numeric(16, 10), scale_y::numeric(16, 10), blocksize_x, blocksize_y, same_alignment, regular_blocking, num_bands, pixel_types, nodata_values::numeric(16,10)[], out_db, ST_AsEWKT(extent) FROM raster_columns WHERE r_table_name = 'loadedrast' AND r_raster_column = 'rast';
+
+-- The raster data should survive the long maintenance path unchanged.
+SELECT ST_AsEWKT(geom), val FROM (SELECT (ST_PixelAsPolygons(rast, 1)).* FROM loadedrast WHERE rid = 1) foo WHERE x = 1 AND y = 1;
+SELECT ST_AsEWKT(geom), val FROM (SELECT (ST_PixelAsPolygons(rast, 2)).* FROM loadedrast WHERE rid = 1) foo WHERE x = 90 AND y = 50;
+SELECT ST_AsEWKT(geom), val FROM (SELECT (ST_PixelAsPolygons(rast, 3)).* FROM loadedrast WHERE rid = 1) foo WHERE x = 45 AND y = 25;
diff --git a/raster/test/regress/loader/LongMaintenance.tif.ref b/raster/test/regress/loader/LongMaintenance.tif.ref
new file mode 100644
index 000000000..1e1cc0fc8
--- /dev/null
+++ b/raster/test/regress/loader/LongMaintenance.tif.ref
@@ -0,0 +1 @@
+testraster.tif
diff --git a/raster/test/regress/loader/LongOptions.opts b/raster/test/regress/loader/LongOptions.opts
new file mode 100644
index 000000000..628a4b62b
--- /dev/null
+++ b/raster/test/regress/loader/LongOptions.opts
@@ -0,0 +1 @@
+--drop-table --create-table --load-data --create-index --if-not-exists --no-transaction
diff --git a/raster/test/regress/loader/LongOptions.select.expected b/raster/test/regress/loader/LongOptions.select.expected
new file mode 100644
index 000000000..47e5ca813
--- /dev/null
+++ b/raster/test/regress/loader/LongOptions.select.expected
@@ -0,0 +1,4 @@
+1
+90|50|3
+255
+1
diff --git a/raster/test/regress/loader/LongOptions.select.sql b/raster/test/regress/loader/LongOptions.select.sql
new file mode 100644
index 000000000..71dc9a290
--- /dev/null
+++ b/raster/test/regress/loader/LongOptions.select.sql
@@ -0,0 +1,12 @@
+-- The long action options should still perform a normal raster load.
+SELECT count(*) FROM loadedrast;
+SELECT ST_Width(rast), ST_Height(rast), ST_NumBands(rast) FROM loadedrast WHERE rid = 1;
+SELECT ST_Value(rast, 1, 1, 1) FROM loadedrast WHERE rid = 1;
+
+-- --create-index combined with --if-not-exists emits a stable named index.
+-- PostgreSQL stores the resulting definition without IF NOT EXISTS.
+SELECT COUNT(*)
+FROM pg_indexes
+WHERE schemaname = 'public'
+ AND tablename = 'loadedrast'
+ AND indexdef LIKE 'CREATE INDEX loadedrast_rast_gist ON public.loadedrast USING gist (st_convexhull(rast))';
diff --git a/raster/test/regress/loader/LongOptions.tif.ref b/raster/test/regress/loader/LongOptions.tif.ref
new file mode 100644
index 000000000..1e1cc0fc8
--- /dev/null
+++ b/raster/test/regress/loader/LongOptions.tif.ref
@@ -0,0 +1 @@
+testraster.tif
diff --git a/raster/test/regress/tests.mk.in b/raster/test/regress/tests.mk.in
index 8fa0cf6d2..7bc6972e6 100644
--- a/raster/test/regress/tests.mk.in
+++ b/raster/test/regress/tests.mk.in
@@ -132,6 +132,8 @@ RASTER_TEST_LOADER = \
$(top_srcdir)/raster/test/regress/loader/Projected \
$(top_srcdir)/raster/test/regress/loader/OverviewNoPadding \
$(top_srcdir)/raster/test/regress/loader/IfNotExists \
+ $(top_srcdir)/raster/test/regress/loader/LongOptions \
+ $(top_srcdir)/raster/test/regress/loader/LongMaintenance \
$(top_srcdir)/raster/test/regress/loader/BasicCopy \
$(top_srcdir)/raster/test/regress/loader/BasicFilename \
$(top_srcdir)/raster/test/regress/loader/BasicOutDB \
diff --git a/regress/loader/LongOptions.dbf b/regress/loader/LongOptions.dbf
new file mode 100644
index 000000000..13b4aeea0
Binary files /dev/null and b/regress/loader/LongOptions.dbf differ
diff --git a/regress/loader/LongOptions.opts b/regress/loader/LongOptions.opts
new file mode 100644
index 000000000..098600e4b
--- /dev/null
+++ b/regress/loader/LongOptions.opts
@@ -0,0 +1,3 @@
+# Exercise the public long action names as one loader invocation:
+# drop, create-if-missing, load, idempotent index, no transaction, and analyze.
+--drop-table --create-table --load-data --create-index --if-not-exists --no-transaction --no-analyze --analyze
diff --git a/regress/loader/LongOptions.select.expected b/regress/loader/LongOptions.select.expected
new file mode 100644
index 000000000..f00580c40
--- /dev/null
+++ b/regress/loader/LongOptions.select.expected
@@ -0,0 +1,2 @@
+3
+1
diff --git a/regress/loader/LongOptions.select.sql b/regress/loader/LongOptions.select.sql
new file mode 100644
index 000000000..e49ab059c
--- /dev/null
+++ b/regress/loader/LongOptions.select.sql
@@ -0,0 +1,10 @@
+-- The long action options should still perform a normal load.
+SELECT count(*) FROM loadedshp;
+
+-- --create-index combined with --if-not-exists emits a stable named index.
+-- PostgreSQL stores the resulting definition without IF NOT EXISTS.
+SELECT COUNT(*)
+FROM pg_indexes
+WHERE schemaname = 'public'
+ AND tablename = 'loadedshp'
+ AND indexdef LIKE 'CREATE INDEX loadedshp_the_geom_gist ON public.loadedshp USING gist (the_geom)';
diff --git a/regress/loader/LongOptions.shp b/regress/loader/LongOptions.shp
new file mode 100644
index 000000000..51d38e75d
Binary files /dev/null and b/regress/loader/LongOptions.shp differ
diff --git a/regress/loader/LongOptions.shx b/regress/loader/LongOptions.shx
new file mode 100644
index 000000000..7db02aff8
Binary files /dev/null and b/regress/loader/LongOptions.shx differ
diff --git a/regress/loader/TestANALYZE.sql.expected b/regress/loader/LongOptions.sql.expected
similarity index 61%
copy from regress/loader/TestANALYZE.sql.expected
copy to regress/loader/LongOptions.sql.expected
index fe56dd4b1..59151e1a3 100644
--- a/regress/loader/TestANALYZE.sql.expected
+++ b/regress/loader/LongOptions.sql.expected
@@ -1,12 +1,10 @@
SET CLIENT_ENCODING TO UTF8;
SET STANDARD_CONFORMING_STRINGS TO ON;
-BEGIN;
-CREATE TABLE "loadedshp" (gid serial);
-ALTER TABLE "loadedshp" ADD PRIMARY KEY (gid);
-SELECT AddGeometryColumn('','loadedshp','the_geom','0','POINT',2);
+DROP TABLE IF EXISTS "loadedshp";
+CREATE TABLE IF NOT EXISTS "loadedshp" (gid serial PRIMARY KEY,
+"the_geom" geometry(POINT,0));
INSERT INTO "loadedshp" (the_geom) VALUES ('01010000000000000000000000000000000000F03F');
INSERT INTO "loadedshp" (the_geom) VALUES ('01010000000000000000002240000000000000F0BF');
INSERT INTO "loadedshp" (the_geom) VALUES ('01010000000000000000002240000000000000F0BF');
-CREATE INDEX ON "loadedshp" USING GIST ("the_geom");
-COMMIT;
+CREATE INDEX IF NOT EXISTS "loadedshp_the_geom_gist" ON "loadedshp" USING GIST ("the_geom");
ANALYZE "loadedshp";
diff --git a/regress/loader/TestANALYZE.opts b/regress/loader/TestANALYZE.opts
index c865f6dca..2f7d46ac5 100644
--- a/regress/loader/TestANALYZE.opts
+++ b/regress/loader/TestANALYZE.opts
@@ -1,2 +1,2 @@
-# Test with ANALYZE and index creation
--I -g the_geom {regdir}/loader/TestSkipANALYZE loadedshp
+# Test long --analyze with short index creation.
+-I --analyze -g the_geom {regdir}/loader/TestSkipANALYZE loadedshp
diff --git a/regress/loader/TestANALYZE.sql.expected b/regress/loader/TestANALYZE.sql.expected
index fe56dd4b1..10769aea0 100644
--- a/regress/loader/TestANALYZE.sql.expected
+++ b/regress/loader/TestANALYZE.sql.expected
@@ -1,9 +1,9 @@
SET CLIENT_ENCODING TO UTF8;
SET STANDARD_CONFORMING_STRINGS TO ON;
BEGIN;
-CREATE TABLE "loadedshp" (gid serial);
+CREATE TABLE "loadedshp" (gid serial,
+"the_geom" geometry(POINT,0));
ALTER TABLE "loadedshp" ADD PRIMARY KEY (gid);
-SELECT AddGeometryColumn('','loadedshp','the_geom','0','POINT',2);
INSERT INTO "loadedshp" (the_geom) VALUES ('01010000000000000000000000000000000000F03F');
INSERT INTO "loadedshp" (the_geom) VALUES ('01010000000000000000002240000000000000F0BF');
INSERT INTO "loadedshp" (the_geom) VALUES ('01010000000000000000002240000000000000F0BF');
diff --git a/regress/loader/TestSkipANALYZE.opts b/regress/loader/TestSkipANALYZE.opts
index 4ffc225f6..cbfc8e6cd 100644
--- a/regress/loader/TestSkipANALYZE.opts
+++ b/regress/loader/TestSkipANALYZE.opts
@@ -1,2 +1,2 @@
-# Test -Z skips ANALYZE
--Z -g the_geom {regdir}/loader/TestSkipANALYZE loadedshp
+# Test long --no-analyze skips ANALYZE.
+--no-analyze -g the_geom {regdir}/loader/TestSkipANALYZE loadedshp
diff --git a/regress/loader/tests.mk b/regress/loader/tests.mk
index ac4f8ad23..40941b7e6 100644
--- a/regress/loader/tests.mk
+++ b/regress/loader/tests.mk
@@ -38,7 +38,7 @@ TESTS += \
$(top_srcdir)/regress/loader/Latin1 \
$(top_srcdir)/regress/loader/Latin1-implicit \
$(top_srcdir)/regress/loader/mfile \
+ $(top_srcdir)/regress/loader/LongOptions \
$(top_srcdir)/regress/loader/TestSkipANALYZE \
$(top_srcdir)/regress/loader/TestANALYZE \
$(top_srcdir)/regress/loader/CharNoWidth \
-
commit 37db33a47b8d380d6b58f8fcdf4824bfa0f2c847
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sun Jun 21 00:24:08 2026 +0400
loader: add shp2pgsql --drop-table option
Add --drop-table to request a DROP TABLE IF EXISTS statement before create/prepare output without changing mutually exclusive load modes. Keep -d as the drop/create/load preset and reject --drop-table -a because append mode has no create step to precede.
Closes #2935
Closes https://github.com/postgis/postgis/pull/1048
diff --git a/NEWS b/NEWS
index 121538c26..495b17930 100644
--- a/NEWS
+++ b/NEWS
@@ -23,6 +23,8 @@ To take advantage of all postgis_sfcgal extension features SFCGAL 2.3+ is needed
- #1124, shp2pgsql can create UNLOGGED tables for transient staging loads
(Darafei Praliaskouski)
+ - #2935, shp2pgsql --drop-table can emit DROP TABLE before prepare output
+ (Darafei Praliaskouski)
- #4208, Add single-geometry variants of ST_MaxDistance and ST_LongestLine
(Darafei Praliaskouski)
- [topology] FindVertexSegmentPairsBelowDistance function (Sandro Santilli)
diff --git a/doc/man/shp2pgsql.1 b/doc/man/shp2pgsql.1
index 37ccabcba..8ccc5d1bb 100644
--- a/doc/man/shp2pgsql.1
+++ b/doc/man/shp2pgsql.1
@@ -48,6 +48,10 @@ Only produces the table creation SQL code, without adding any actual data.
This can be used if you need to completely separate the table creation and
data loading steps.
.TP
+\fB\-\-drop\-table\fR
+Emits a DROP TABLE IF EXISTS statement before table creation. This can be
+combined with \-c or \-p, but not with \-a.
+.TP
\fB\-D\fR
Use the PostgreSQL "dump" format for the output data. This can be combined
with \-a, \-c and \-d. It is much faster to load than the default "insert"
diff --git a/loader/README.shp2pgsql b/loader/README.shp2pgsql
index 08ecde1f7..d5afc8e19 100644
--- a/loader/README.shp2pgsql
+++ b/loader/README.shp2pgsql
@@ -48,6 +48,10 @@ OPTIONS
actual data. This can be used if you need to completely sepa-
rate the table creation and data loading steps.
+ --drop-table
+ Emits a DROP TABLE IF EXISTS statement before table creation.
+ This can be combined with -c or -p, but not with -a.
+
-D Use the PostgreSQL "dump" format for the output data. This can
be combined with -a, -c and -d. It is much faster to load than
the default "insert" SQL format. Use this for very large data
diff --git a/loader/cunit/cu_shp2pgsql.c b/loader/cunit/cu_shp2pgsql.c
index ee6159cb0..0eefab957 100644
--- a/loader/cunit/cu_shp2pgsql.c
+++ b/loader/cunit/cu_shp2pgsql.c
@@ -12,10 +12,12 @@
#include "cu_shp2pgsql.h"
#include "cu_tester.h"
#include "../shp2pgsql-core.h"
+#include <string.h>
/* Test functions */
void test_ShpLoaderCreate(void);
void test_ShpLoaderDestroy(void);
+void test_ShpLoaderGetSQLHeader_drop_prepare(void);
SHPLOADERCONFIG *loader_config;
SHPLOADERSTATE *loader_state;
@@ -33,10 +35,10 @@ CU_pSuite register_shp2pgsql_suite(void)
return NULL;
}
- if (
- (NULL == CU_add_test(pSuite, "test_ShpLoaderCreate()", test_ShpLoaderCreate)) ||
- (NULL == CU_add_test(pSuite, "test_ShpLoaderDestroy()", test_ShpLoaderDestroy))
- )
+ if ((NULL == CU_add_test(pSuite, "test_ShpLoaderCreate()", test_ShpLoaderCreate)) ||
+ (NULL == CU_add_test(pSuite, "test_ShpLoaderDestroy()", test_ShpLoaderDestroy)) ||
+ (NULL ==
+ CU_add_test(pSuite, "test_ShpLoaderGetSQLHeader_drop_prepare()", test_ShpLoaderGetSQLHeader_drop_prepare)))
{
CU_cleanup_registry();
return NULL;
@@ -75,3 +77,34 @@ void test_ShpLoaderDestroy(void)
{
ShpLoaderDestroy(loader_state);
}
+
+void
+test_ShpLoaderGetSQLHeader_drop_prepare(void)
+{
+ char *header = NULL;
+ const char *drop;
+ const char *create;
+
+ loader_config = (SHPLOADERCONFIG *)calloc(1, sizeof(SHPLOADERCONFIG));
+ set_loader_config_defaults(loader_config);
+ loader_config->opt = 'p';
+ loader_config->drop_table = 1;
+ /* This header-only test does not open a shapefile, so avoid geometry metadata. */
+ loader_config->readshape = 0;
+ loader_config->table = "loadedshp";
+ loader_config->geo_col = "the_geom";
+
+ loader_state = ShpLoaderCreate(loader_config);
+ CU_ASSERT_EQUAL(ShpLoaderGetSQLHeader(loader_state, &header), SHPLOADEROK);
+ CU_ASSERT_PTR_NOT_NULL(header);
+
+ drop = strstr(header, "DROP TABLE IF EXISTS \"loadedshp\";");
+ create = strstr(header, "CREATE TABLE \"loadedshp\"");
+ CU_ASSERT_PTR_NOT_NULL(drop);
+ CU_ASSERT_PTR_NOT_NULL(create);
+ CU_ASSERT_PTR_NULL(strstr(header, "DropGeometryColumn"));
+ CU_ASSERT(drop < create);
+
+ free(header);
+ ShpLoaderDestroy(loader_state);
+}
diff --git a/loader/shp2pgsql-cli.c b/loader/shp2pgsql-cli.c
index 784ba3a7e..28d1a1c25 100644
--- a/loader/shp2pgsql-cli.c
+++ b/loader/shp2pgsql-cli.c
@@ -63,6 +63,7 @@ usage()
" attribute column. (default: \"UTF-8\")\n" ));
printf(_( " -N <policy> NULL geometries handling policy (insert*,skip,abort).\n" ));
printf(_( " -n Only import DBF file.\n" ));
+ printf(_(" --drop-table Emit DROP TABLE IF EXISTS before create or prepare output.\n"));
printf(_( " -T <tablespace> Specify the tablespace for the new table.\n"
" Note that indexes will still use the default tablespace unless the\n"
" -X flag is also used.\n"));
@@ -106,8 +107,19 @@ main (int argc, char **argv)
set_loader_config_defaults(config);
/* Keep the flag list alphabetic so it's easy to see what's left. */
- while ((c = pgis_getopt(argc, argv, "-?acdeg:ikm:nps:t:uwDGIN:ST:W:X:Z")) != EOF)
+ while (pgis_optind < argc)
{
+ if (strcmp(argv[pgis_optind], "--drop-table") == 0)
+ {
+ config->drop_table = 1;
+ pgis_optind++;
+ continue;
+ }
+
+ c = pgis_getopt(argc, argv, "-?acdeg:ikm:nps:t:uwDGIN:ST:W:X:Z");
+ if (c == EOF)
+ break;
+
// can not do this inside the switch case
if ('-' == c)
break;
@@ -267,6 +279,12 @@ main (int argc, char **argv)
exit(1);
}
+ if (config->drop_table && config->opt == 'a')
+ {
+ fprintf(stderr, "Invalid argument combination - cannot use both --drop-table and -a\n");
+ exit(1);
+ }
+
/* Determine the shapefile name from the next argument, if no shape file, exit. */
if (pgis_optind < argc)
{
@@ -386,7 +404,7 @@ main (int argc, char **argv)
free(header);
/* If we are not in "prepare" mode, go ahead and write out the data. */
- if ( state->config->opt != 'p' )
+ if (state->config->load_data)
{
/* If in COPY mode, output the COPY statement */
diff --git a/loader/shp2pgsql-core.c b/loader/shp2pgsql-core.c
index ac1adc528..fa8bffe75 100644
--- a/loader/shp2pgsql-core.c
+++ b/loader/shp2pgsql-core.c
@@ -771,6 +771,10 @@ set_loader_config_defaults(SHPLOADERCONFIG *config)
config->forceint4 = 0;
config->createindex = 0;
config->unlogged = 0;
+ config->drop_table = 0;
+ config->drop_geometry_column = 0;
+ config->create_table = 1;
+ config->load_data = 1;
config->analyze = 1;
config->readshape = 1;
config->force_output = FORCE_OUTPUT_DISABLE;
@@ -785,12 +789,25 @@ set_loader_config_defaults(SHPLOADERCONFIG *config)
config->column_map_filename = NULL;
}
+static void
+set_loader_config_actions(SHPLOADERCONFIG *config)
+{
+ const int explicit_drop_table = config->drop_table;
+
+ config->drop_table = explicit_drop_table || config->opt == 'd';
+ config->drop_geometry_column = config->opt == 'd';
+ config->create_table = config->opt != 'a';
+ config->load_data = config->opt != 'p';
+}
+
/* Create a new shapefile state object */
SHPLOADERSTATE *
ShpLoaderCreate(SHPLOADERCONFIG *config)
{
SHPLOADERSTATE *state;
+ set_loader_config_actions(config);
+
/* Create a new state object and assign the config to it */
state = malloc(sizeof(SHPLOADERSTATE));
state->config = config;
@@ -1300,7 +1317,7 @@ ShpLoaderGetSQLHeader(SHPLOADERSTATE *state, char **strheader)
stringbuffer_aprintf(sb, "SET STANDARD_CONFORMING_STRINGS TO ON;\n");
/* Drop table if requested */
- if (state->config->opt == 'd')
+ if (state->config->drop_table)
{
/**
* TODO: if the table has more then one geometry column
@@ -1315,7 +1332,8 @@ ShpLoaderGetSQLHeader(SHPLOADERSTATE *state, char **strheader)
*/
if (state->config->schema)
{
- if (state->config->readshape == 1 && (! state->config->geography) )
+ if (state->config->drop_geometry_column && state->config->readshape == 1 &&
+ (!state->config->geography))
{
stringbuffer_aprintf(sb, "SELECT DropGeometryColumn('%s','%s','%s');\n",
state->config->schema, state->config->table, state->geo_col);
@@ -1326,7 +1344,8 @@ ShpLoaderGetSQLHeader(SHPLOADERSTATE *state, char **strheader)
}
else
{
- if (state->config->readshape == 1 && (! state->config->geography) )
+ if (state->config->drop_geometry_column && state->config->readshape == 1 &&
+ (!state->config->geography))
{
stringbuffer_aprintf(sb, "SELECT DropGeometryColumn('','%s','%s');\n",
state->config->table, state->geo_col);
@@ -1342,8 +1361,8 @@ ShpLoaderGetSQLHeader(SHPLOADERSTATE *state, char **strheader)
stringbuffer_aprintf(sb, "BEGIN;\n");
}
- /* If not in 'append' mode create the spatial table */
- if (state->config->opt != 'a')
+ /* Create the spatial table when requested by the selected actions. */
+ if (state->config->create_table)
{
/*
* Create a table for inserting the shapes into with appropriate
diff --git a/loader/shp2pgsql-core.h b/loader/shp2pgsql-core.h
index fd078e21b..ed8e5ae71 100644
--- a/loader/shp2pgsql-core.h
+++ b/loader/shp2pgsql-core.h
@@ -81,7 +81,7 @@
*/
typedef struct shp_loader_config
{
- /* load mode: c = create, d = delete, a = append, p = prepare */
+ /* load mode preset: c = create, d = drop/create, a = append, p = prepare */
char opt;
/* table to load into */
@@ -117,7 +117,19 @@ typedef struct shp_loader_config
/* 0 = logged table, 1 = create as UNLOGGED table */
int unlogged;
- /* 0 = don't analyze tables , 1 = analyze tables */
+ /* action: emit DROP TABLE before create/prepare */
+ int drop_table;
+
+ /* action: remove geometry_columns entry before dropping the table */
+ int drop_geometry_column;
+
+ /* action: create the target table */
+ int create_table;
+
+ /* action: load rows from the input file */
+ int load_data;
+
+ /* 0 = don't analyze tables , 1 = analyze tables */
int analyze;
/* 0 = load DBF file only, 1 = load everything */
diff --git a/loader/shp2pgsql-gui.c b/loader/shp2pgsql-gui.c
index 7f8dbbc87..853a8c3af 100644
--- a/loader/shp2pgsql-gui.c
+++ b/loader/shp2pgsql-gui.c
@@ -1609,8 +1609,8 @@ pgui_action_import(GtkWidget *widget, gpointer data)
if (!ret)
goto import_cleanup;
- /* If we are in prepare mode, we need to skip the actual load. */
- if (state->config->opt != 'p')
+ /* Load rows when requested by the selected actions. */
+ if (state->config->load_data)
{
int numrecords = ShpLoaderGetRecordCount(state);
int records_per_tick = (numrecords / 200) - 1;
@@ -1714,7 +1714,7 @@ pgui_action_import(GtkWidget *widget, gpointer data)
goto import_cleanup;
}
}
- } /* if (state->config->opt != 'p') */
+ } /* if (state->config->load_data) */
/* Only continue if we didn't abort part way through */
if (is_running)
@@ -1756,7 +1756,7 @@ import_cleanup:
pg_connection = NULL;
/* If we didn't finish inserting all of the items (and we expected to), an error occurred */
- if ((state->config->opt != 'p' && i != ShpLoaderGetRecordCount(state)) || !ret)
+ if ((state->config->load_data && i != ShpLoaderGetRecordCount(state)) || !ret)
pgui_logf(_("Shapefile import failed."));
else
pgui_logf(_("Shapefile import completed."));
-----------------------------------------------------------------------
Summary of changes:
NEWS | 6 +-
doc/man/raster2pgsql.1 | 28 ++-
doc/man/shp2pgsql.1 | 28 ++-
doc/using_raster_dataman.xml | 24 +-
loader/Makefile.in | 4 +-
loader/README.shp2pgsql | 26 ++
loader/cunit/Makefile.in | 2 +
loader/cunit/cu_shp2pgsql.c | 108 +++++++-
loader/loader_actions.h | 67 +++++
loader/shp2pgsql-cli.c | 125 +++++++++-
loader/shp2pgsql-core.c | 271 ++++++++++++---------
loader/shp2pgsql-core.h | 13 +-
loader/shp2pgsql-gui.c | 44 ++--
raster/loader/Makefile.in | 2 +-
raster/loader/raster2pgsql.c | 236 +++++++++++++-----
raster/loader/raster2pgsql.h | 32 +--
raster/test/regress/loader/LongMaintenance.opts | 2 +
...ct.expected => LongMaintenance.select.expected} | 0
...ename.select.sql => LongMaintenance.select.sql} | 5 +-
.../{Basic.tif.ref => LongMaintenance.tif.ref} | 0
raster/test/regress/loader/LongOptions.opts | 1 +
...select.expected => LongOptions.select.expected} | 0
...NotExists.select.sql => LongOptions.select.sql} | 4 +
.../loader/{Basic.tif.ref => LongOptions.tif.ref} | 0
raster/test/regress/tests.mk.in | 2 +
regress/loader/{ArcM.dbf => LongOptions.dbf} | Bin
regress/loader/LongOptions.opts | 3 +
...select.expected => LongOptions.select.expected} | 2 +-
regress/loader/LongOptions.select.sql | 10 +
.../loader/{NoTransPoint.shp => LongOptions.shp} | Bin
.../loader/{NoTransPoint.shx => LongOptions.shx} | Bin
...ALYZE.sql.expected => LongOptions.sql.expected} | 10 +-
regress/loader/TestANALYZE.opts | 4 +-
regress/loader/TestANALYZE.sql.expected | 4 +-
regress/loader/TestSkipANALYZE.opts | 4 +-
regress/loader/tests.mk | 2 +-
36 files changed, 815 insertions(+), 254 deletions(-)
create mode 100644 loader/loader_actions.h
create mode 100644 raster/test/regress/loader/LongMaintenance.opts
copy raster/test/regress/loader/{BasicCopy.select.expected => LongMaintenance.select.expected} (100%)
copy raster/test/regress/loader/{BasicFilename.select.sql => LongMaintenance.select.sql} (76%)
copy raster/test/regress/loader/{Basic.tif.ref => LongMaintenance.tif.ref} (100%)
create mode 100644 raster/test/regress/loader/LongOptions.opts
copy raster/test/regress/loader/{IfNotExists.select.expected => LongOptions.select.expected} (100%)
copy raster/test/regress/loader/{IfNotExists.select.sql => LongOptions.select.sql} (64%)
copy raster/test/regress/loader/{Basic.tif.ref => LongOptions.tif.ref} (100%)
copy regress/loader/{ArcM.dbf => LongOptions.dbf} (100%)
create mode 100644 regress/loader/LongOptions.opts
copy regress/loader/{TestANALYZE.select.expected => LongOptions.select.expected} (50%)
create mode 100644 regress/loader/LongOptions.select.sql
copy regress/loader/{NoTransPoint.shp => LongOptions.shp} (100%)
copy regress/loader/{NoTransPoint.shx => LongOptions.shx} (100%)
copy regress/loader/{TestANALYZE.sql.expected => LongOptions.sql.expected} (61%)
hooks/post-receive
--
PostGIS
More information about the postgis-tickets
mailing list