Skip to content

Commit

Permalink
PG: add escaping syntax for TABLES open option to allow table names w…
Browse files Browse the repository at this point in the history
…ith open parenthesis

Fixes #11486
  • Loading branch information
rouault committed Dec 13, 2024
1 parent 6ebfe07 commit 412c693
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 24 deletions.
41 changes: 41 additions & 0 deletions autotest/ogr/ogr_pg.py
Original file line number Diff line number Diff line change
Expand Up @@ -1621,6 +1621,47 @@ def test_ogr_pg_31(pg_ds):
assert i == 501


###############################################################################
# Test the TABLES open option


def test_ogr_pg_tables_open_option(pg_ds):

pg_ds.CreateLayer(
"test (with parenthesis and \\)",
geom_type=ogr.wkbPoint,
)
pg_ds.CreateLayer(
"test with, comma",
geom_type=ogr.wkbPoint,
)
pg_ds.FlushCache()

with gdal.OpenEx(
pg_ds.GetDescription(),
gdal.OF_VECTOR,
open_options=["TABLES=test \\(with parenthesis and \\\\)"],
) as ds:
assert ds.GetLayerCount() == 1
assert ds.GetLayer(0).GetName() == "test (with parenthesis and \\)"

with gdal.OpenEx(
pg_ds.GetDescription(),
gdal.OF_VECTOR,
open_options=["TABLES=test \\(with parenthesis and \\\\)(geometry)"],
) as ds:
assert ds.GetLayerCount() == 1
assert ds.GetLayer(0).GetName() == "test (with parenthesis and \\)"

with gdal.OpenEx(
pg_ds.GetDescription(),
gdal.OF_VECTOR,
open_options=['TABLES="test with, comma"'],
) as ds:
assert ds.GetLayerCount() == 1
assert ds.GetLayer(0).GetName() == "test with, comma"


###############################################################################
# Test approximate srtext (#2123, #3508)

Expand Down
9 changes: 9 additions & 0 deletions doc/source/drivers/vector/pg.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,15 @@ The following open options are supported:
- .. oo:: TABLES

Restricted set of tables to list (comma separated).
Each table name can be qualified by its schema, like ``schema_name.table_name``.
For tables with multiple geometry columns, the ``table_name(geometry_column)``
syntax can be used.

Starting with GDAL 3.11, if the table name itself contains an open parenthesis ``(``,
or a backslash character ``\``, they must be escaped with a preceding backslash
character. e.g. ``table_name \(with parenthesis)``.
If that table name contains a comma, the table name must be quoted with
double quotes. e.g. ``first_table,"second table, with comma"``.

- .. oo:: LIST_ALL_TABLES
:choices: YES, NO
Expand Down
97 changes: 73 additions & 24 deletions ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -920,60 +920,109 @@ void OGRPGDataSource::LoadTables()

if (pszForcedTables)
{
char **papszTableList = CSLTokenizeString2(pszForcedTables, ",", 0);
const CPLStringList aosTableList(
CSLTokenizeString2(pszForcedTables, ",", CSLT_HONOURSTRINGS));

for (int i = 0; i < CSLCount(papszTableList); i++)
for (const char *pszTable : aosTableList)
{
// Get schema and table name
char **papszQualifiedParts =
CSLTokenizeString2(papszTableList[i], ".", 0);
int nParts = CSLCount(papszQualifiedParts);
const CPLStringList aosQualifiedParts(
CSLTokenizeString2(pszTable, ".", CSLT_HONOURSTRINGS));
const int nParts = aosQualifiedParts.size();

if (nParts == 1 || nParts == 2)
{
/* Find the geometry column name if specified */
char *pszGeomColumnName = nullptr;
char *pos = strchr(
papszQualifiedParts[CSLCount(papszQualifiedParts) - 1],
'(');
if (pos != nullptr)
std::string osTableName;
std::string osGeomColumnName;
const char *pszTablePart = aosQualifiedParts[nParts - 1];
int j = 0;
for (; pszTablePart[j]; ++j)
{
*pos = '\0';
pszGeomColumnName = pos + 1;
int len = static_cast<int>(strlen(pszGeomColumnName));
if (len > 0)
pszGeomColumnName[len - 1] = '\0';
if (pszTablePart[j] == '\\')
{
if (pszTablePart[j + 1] == '\\' ||
pszTablePart[j + 1] == '(')
{
++j;
osTableName += pszTablePart[j];
}
else
{
CPLError(CE_Failure, CPLE_AppDefined,
"Unexpected character after backslash at "
"offset %d of %s",
j, pszTablePart);
}
}
else if (pszTablePart[j] == '(')
{
// Now parse the geometry column name
break;
}
else
{
osTableName += pszTablePart[j];
}
}

if (pszTablePart[j] == '(')
{
// Now parse the geometry column name
++j;
for (; pszTablePart[j]; ++j)
{
if (pszTablePart[j] == '\\')
{
if (pszTablePart[j + 1] == '\\' ||
pszTablePart[j + 1] == '(')
{
++j;
osGeomColumnName += pszTablePart[j];
}
else
{
CPLError(CE_Failure, CPLE_AppDefined,
"Unexpected character after backslash "
"at offset %d of %s",
j, pszTablePart);
}
}
else
{
osGeomColumnName += pszTablePart[j];
}
}
if (!osGeomColumnName.empty() &&
osGeomColumnName.back() == ')')
osGeomColumnName.pop_back();
}

papsTables = static_cast<PGTableEntry **>(CPLRealloc(
papsTables, sizeof(PGTableEntry *) * (nTableCount + 1)));
papsTables[nTableCount] = static_cast<PGTableEntry *>(
CPLCalloc(1, sizeof(PGTableEntry)));
if (pszGeomColumnName)
if (!osGeomColumnName.empty())
OGRPGTableEntryAddGeomColumn(papsTables[nTableCount],
pszGeomColumnName);
osGeomColumnName.c_str());

if (nParts == 2)
{
papsTables[nTableCount]->pszSchemaName =
CPLStrdup(papszQualifiedParts[0]);
CPLStrdup(aosQualifiedParts[0]);
papsTables[nTableCount]->pszTableName =
CPLStrdup(papszQualifiedParts[1]);
CPLStrdup(osTableName.c_str());
}
else
{
papsTables[nTableCount]->pszSchemaName =
CPLStrdup(osActiveSchema.c_str());
papsTables[nTableCount]->pszTableName =
CPLStrdup(papszQualifiedParts[0]);
CPLStrdup(osTableName.c_str());
}
nTableCount++;
}

CSLDestroy(papszQualifiedParts);
}

CSLDestroy(papszTableList);
}

/* -------------------------------------------------------------------- */
Expand Down

0 comments on commit 412c693

Please sign in to comment.