Skip to content

Commit 9ee6883

Browse files
committed
Merge pull request #47 from robe2/master
#10 PostgreSQL 9.5 IMPORT FOREIGN SCHEMA support
2 parents 9c32a63 + d64e14a commit 9ee6883

File tree

4 files changed

+395
-2
lines changed

4 files changed

+395
-2
lines changed

README.md

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,5 +189,65 @@ Wraparound action! Handy for testing. Connect your database back to your databas
189189
OPTIONS ( layer 'typetest' );
190190

191191
SELECT * FROM typetest_fdw;
192-
193-
Enjoy!
192+
193+
### Using IMPORT FOREIGN SCHEMA (for PostgreSQL 9.5+ only)
194+
195+
## Importing links to all tables
196+
If you want to import all tables use the special schema called *ogr_all*
197+
198+
CREATE SCHEMA fgdball;
199+
IMPORT FOREIGN SCHEMA ogr_all
200+
FROM server fgdbtest INTO fgdball;
201+
202+
## Importing subset of tables using prefixes
203+
Not all ogr data sources have a concept of schema, so we use the remote_schema as a prefix.
204+
Note this is case sensitive, so make sure casing matches your layer names.
205+
206+
For example the following will only import tables that start with *CitiesIn*. As long as you quote, you can handle
207+
true schemaed databases such as SQL server or PostgreSQL by using something like *"dbo."*
208+
209+
CREATE SCHEMA fgdbcityinf;
210+
IMPORT FOREIGN SCHEMA "CitiesIn"
211+
FROM server fgdbtest INTO fgdbcityinf;
212+
213+
## Preserving case and special characters in column names and table names
214+
By default, when IMPORT FOREIGN SCHEMA is run on an ogr foreign data server, the table names and column names are laundered
215+
(meaning all upper case is converted to lowercase and special characters such as spaces are replaced with _).
216+
217+
This is not desirable in all cases. You can override this behavior with 2 IMPORT FOREIGN SCHEMA options specific to ogr fdw servers.
218+
219+
These are `launder_column_names` and `launder_tables_names`.
220+
221+
To preserve casing and other funky characters in both column names and table names you can do the following:
222+
223+
CREATE SCHEMA fgdbcitypreserve;
224+
IMPORT FOREIGN SCHEMA ogr_all
225+
FROM server fgdbtest INTO fgdbpreserve
226+
OPTIONS(launder_table_names 'false', launder_column_names 'false') ;
227+
228+
229+
## Importing subset of layers using LIMIT and EXCEPT
230+
Note: LIMIT TO /EXCEPT should contain resulting table names (NOT the layer names)
231+
In the default case, the table names are laundered should not have mixed case or weird characters.
232+
233+
CREATE SCHEMA fgdbcitysub;
234+
-- import only layer called Cities
235+
IMPORT FOREIGN SCHEMA ogr_all
236+
LIMIT TO(cities)
237+
FROM server fgdbtest INTO fgdbcitysub ;
238+
239+
-- import only layers not called Cities or Countries
240+
IMPORT FOREIGN SCHEMA ogr_all
241+
EXCEPT (cities, countries)
242+
FROM server fgdbtest INTO fgdbcitysub;
243+
244+
-- With table laundering turned off, need to use exact layer names
245+
DROP SCHEMA IF EXISTS fgdbcitysub CASCADE;
246+
247+
IMPORT FOREIGN SCHEMA ogr_all
248+
LIMIT TO("Cities")
249+
FROM server fgdbtest INTO fgdbcitysub OPTIONS(launder_table_names 'false') ;
250+
251+
252+
253+
Enjoy!

ogr_fdw.c

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ static TupleTableSlot *ogrIterateForeignScan(ForeignScanState *node);
104104
static void ogrReScanForeignScan(ForeignScanState *node);
105105
static void ogrEndForeignScan(ForeignScanState *node);
106106

107+
static void strTableColumnLaunder (char *str);
108+
109+
#if PG_VERSION_NUM >= 90500
110+
/*
111+
* Require PostgreSQL >= 9.5
112+
*/
113+
static List *ogrImportForeignSchema(ImportForeignSchemaStmt *stmt,
114+
Oid serverOid);
115+
#endif
116+
107117
/*
108118
* Helper functions
109119
*/
@@ -115,6 +125,8 @@ static void ogr_fdw_exit(int code, Datum arg);
115125
/* Global to hold GEOMETRYOID */
116126
Oid GEOMETRYOID = InvalidOid;
117127

128+
#define STR_MAX_LEN 256
129+
118130

119131
void
120132
_PG_init(void)
@@ -180,6 +192,12 @@ ogr_fdw_handler(PG_FUNCTION_ARGS)
180192
fdwroutine->IterateForeignScan = ogrIterateForeignScan;
181193
fdwroutine->ReScanForeignScan = ogrReScanForeignScan;
182194
fdwroutine->EndForeignScan = ogrEndForeignScan;
195+
196+
#if PG_VERSION_NUM >= 90500
197+
/* PostgreSQL 9.5+
198+
Support functions for IMPORT FOREIGN SCHEMA */
199+
fdwroutine->ImportForeignSchema = ogrImportForeignSchema;
200+
#endif
183201

184202
PG_RETURN_POINTER(fdwroutine);
185203
}
@@ -1265,3 +1283,288 @@ ogrEndForeignScan(ForeignScanState *node)
12651283

12661284

12671285
#endif /* PostgreSQL 9.3 version check */
1286+
1287+
#if PG_VERSION_NUM >= 90500
1288+
/*
1289+
* PostgreSQL 9.5 or above. Import a foreign schema
1290+
*/
1291+
static List *
1292+
ogrImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
1293+
{
1294+
List *commands = NIL;
1295+
ForeignServer *server;
1296+
1297+
List *options;
1298+
ListCell *cell;
1299+
char *sGeomType;
1300+
bool check_schema = false;
1301+
bool launder_column_names = true;
1302+
bool launder_table_names = true;
1303+
StringInfoData buf;
1304+
OgrConnection ogr;
1305+
int i;
1306+
int j;
1307+
int k;
1308+
char layer_name[STR_MAX_LEN];
1309+
char table_name[STR_MAX_LEN];
1310+
ListCell *lc;
1311+
bool include_item = false;
1312+
OGRDataSourceH ogr_ds = NULL;
1313+
OGRSFDriverH ogr_dr = NULL;
1314+
OGRFeatureDefnH ogr_fd = NULL;
1315+
OGRLayerH ogr_lyr = NULL;
1316+
1317+
/** check table prefix if remote_schema asked for is not ogr_all **/
1318+
check_schema = !( strcmp(stmt->remote_schema, "ogr_all") == 0 );
1319+
1320+
elog(NOTICE, "Check schema %d %s", check_schema, stmt->remote_schema);
1321+
if ( GEOMETRYOID == BYTEAOID){ /* postgis is not in search path */
1322+
sGeomType = "bytea";
1323+
}
1324+
else {
1325+
sGeomType = "geometry";
1326+
}
1327+
1328+
#if GDAL_VERSION_MAJOR >= 2 || GDAL_VERSION_MINOR >= 11
1329+
int geom_field_count;
1330+
#endif
1331+
/* Null all values */
1332+
memset(&ogr, 0, sizeof(OgrConnection));
1333+
1334+
/*
1335+
* Get connection to the foreign server. Connection manager will
1336+
* establish new connection if necessary.
1337+
*/
1338+
server = GetForeignServer(serverOid);
1339+
1340+
/* Read server druver and data source connection string
1341+
*/
1342+
options = NIL;
1343+
options = list_concat(options, server->options);
1344+
foreach(cell, options)
1345+
{
1346+
DefElem *def = (DefElem *) lfirst(cell);
1347+
if (strcmp(def->defname, OPT_SOURCE) == 0)
1348+
ogr.ds_str = defGetString(def);
1349+
if (strcmp(def->defname, OPT_DRIVER) == 0)
1350+
ogr.dr_str = defGetString(def);
1351+
}
1352+
1353+
/* Parse statement laundering options */
1354+
foreach(lc, stmt->options)
1355+
{
1356+
DefElem *def = (DefElem *) lfirst(lc);
1357+
1358+
if (strcmp(def->defname, "launder_column_names") == 0)
1359+
launder_column_names = defGetBoolean(def);
1360+
else if (strcmp(def->defname, "launder_table_names") == 0)
1361+
launder_table_names = defGetBoolean(def);
1362+
else
1363+
ereport(ERROR,
1364+
(errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
1365+
errmsg("invalid option \"%s\"", def->defname)));
1366+
}
1367+
1368+
OGRRegisterAll();
1369+
ogr_ds = OGROpen(ogr.ds_str, FALSE, &ogr_dr);
1370+
1371+
1372+
/* Create workspace for strings */
1373+
initStringInfo(&buf);
1374+
1375+
for ( i = 0; i < OGR_DS_GetLayerCount(ogr_ds); i++ )
1376+
{
1377+
include_item = false;
1378+
ogr_lyr = OGR_DS_GetLayer(ogr_ds, i);
1379+
/* we have a table */
1380+
if ( ogr_lyr )
1381+
{
1382+
/* layer name is never laundered, since it's link back to foreign data */
1383+
strncpy(layer_name, OGR_L_GetName(ogr_lyr), STR_MAX_LEN);
1384+
1385+
/* We need to compare against created table names
1386+
* because postgres does an extra check on create foriegn table
1387+
* and removes statements not in limit
1388+
*/
1389+
/* having this as separate variable since we may choose to launder it */
1390+
strncpy(table_name, OGR_L_GetName(ogr_lyr), STR_MAX_LEN);
1391+
if (launder_table_names){
1392+
strTableColumnLaunder(table_name);
1393+
}
1394+
1395+
/* only include if layer prefix starts with remote schema
1396+
or remote schema is ogr_all */
1397+
include_item = (!check_schema ||
1398+
( strncmp(layer_name, stmt->remote_schema, strlen(stmt->remote_schema) ) == 0 ) );
1399+
/* Apply restrictions for LIMIT TO and EXCEPT */
1400+
if (include_item && ( stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO ||
1401+
stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT ) )
1402+
{
1403+
/* Check if current table is in list of except/include tables */
1404+
/* default state is only true if type is EXCEPT */
1405+
include_item = ( stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT );
1406+
foreach(lc, stmt->table_list)
1407+
{
1408+
RangeVar *rv = (RangeVar *) lfirst(lc);
1409+
if ( strcmp(rv->relname, table_name) == 0 ){
1410+
//elog(NOTICE, "MATCH layer %s, table %s", layer_name, rv->relname );
1411+
/* bit is true on match only if limit to */
1412+
include_item = ( stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO );
1413+
break;
1414+
}
1415+
1416+
}
1417+
}
1418+
}
1419+
1420+
if (include_item){
1421+
resetStringInfo(&buf);
1422+
1423+
if (launder_table_names){
1424+
strTableColumnLaunder(table_name);
1425+
}
1426+
ogr_fd = OGR_L_GetLayerDefn(ogr_lyr);
1427+
if ( !ogr_fd )
1428+
{
1429+
/** TODO raise error **/
1430+
elog(NOTICE, "Error in layer def load %s", layer_name);
1431+
}
1432+
1433+
appendStringInfo(&buf, "CREATE FOREIGN TABLE %s (\n",
1434+
quote_identifier(table_name));
1435+
appendStringInfo(&buf, " fid integer");
1436+
1437+
#if GDAL_VERSION_MAJOR >= 2 || GDAL_VERSION_MINOR >= 11
1438+
geom_field_count = OGR_FD_GetGeomFieldCount(ogr_fd);
1439+
if( geom_field_count == 1 )
1440+
{
1441+
appendStringInfo(&buf, " ,geom %s", sGeomType);
1442+
}
1443+
else
1444+
{
1445+
for ( j = 0; j < geom_field_count; j++ )
1446+
{
1447+
appendStringInfo(&buf, " ,geom%d %s", j + 1, sGeomType);
1448+
}
1449+
}
1450+
#else
1451+
if( OGR_L_GetGeomType(ogr_lyr) != wkbNone )
1452+
appendStringInfo(&buf, " ,geom %s", sGeomType);
1453+
#endif
1454+
1455+
for ( k = 0; k < OGR_FD_GetFieldCount(ogr_fd); k++ )
1456+
{
1457+
char field_name[STR_MAX_LEN];
1458+
OGRFieldDefnH ogr_fld = OGR_FD_GetFieldDefn(ogr_fd, k);
1459+
strncpy(field_name, OGR_Fld_GetNameRef(ogr_fld), STR_MAX_LEN);
1460+
if (launder_column_names){
1461+
strTableColumnLaunder(field_name);
1462+
}
1463+
appendStringInfo(&buf, " , %s ", quote_identifier(field_name));
1464+
switch( OGR_Fld_GetType(ogr_fld) )
1465+
{
1466+
case OFTInteger:
1467+
#if GDAL_VERSION_MAJOR >= 2
1468+
if( OGR_Fld_GetSubType(ogr_fld) == OFSTBoolean )
1469+
appendStringInfoString(&buf,"boolean");
1470+
else
1471+
#endif
1472+
appendStringInfoString(&buf,"integer");
1473+
break;
1474+
case OFTReal:
1475+
appendStringInfoString(&buf,"real");
1476+
break;
1477+
case OFTString:
1478+
appendStringInfoString(&buf,"varchar");
1479+
break;
1480+
case OFTBinary:
1481+
appendStringInfoString(&buf,"bytea");
1482+
break;
1483+
case OFTDate:
1484+
appendStringInfoString(&buf,"date");
1485+
break;
1486+
case OFTTime:
1487+
appendStringInfoString(&buf,"time");
1488+
break;
1489+
case OFTDateTime:
1490+
appendStringInfoString(&buf,"timestamp");
1491+
break;
1492+
case OFTIntegerList:
1493+
appendStringInfoString(&buf,"integer[]");
1494+
break;
1495+
case OFTRealList:
1496+
appendStringInfoString(&buf,"real[]");
1497+
break;
1498+
case OFTStringList:
1499+
appendStringInfoString(&buf,"varchar[]");
1500+
break;
1501+
1502+
#if GDAL_VERSION_MAJOR >= 2
1503+
case OFTInteger64:
1504+
appendStringInfoString(&buf,"bigint");
1505+
break;
1506+
#endif
1507+
default:
1508+
elog(NOTICE, "Unsupported GDAL type '%s'", OGR_GetFieldTypeName(OGR_Fld_GetType(ogr_fld)) );
1509+
//CPLError(CE_Failure, CPLE_AppDefined, "Unsupported GDAL type '%s'", OGR_GetFieldTypeName(OGR_Fld_GetType(ogr_fld)));
1510+
//return OGRERR_FAILURE;
1511+
}
1512+
}
1513+
1514+
/*
1515+
* Add server name and layer-level options. We specify remote
1516+
* layer name as option
1517+
*/
1518+
appendStringInfo(&buf, "\n) SERVER %s\nOPTIONS (",
1519+
quote_identifier(server->servername));
1520+
1521+
appendStringInfoString(&buf, "layer ");
1522+
ogrDeparseStringLiteral(&buf, layer_name);
1523+
1524+
appendStringInfoString(&buf, ");");
1525+
1526+
commands = lappend(commands, pstrdup(buf.data));
1527+
}
1528+
}
1529+
OGR_DS_Destroy(ogr_ds);
1530+
elog(NOTICE, "Number of tables to be created %d", list_length(commands) );
1531+
//elog(NOTICE, "The nth item %s", list_nth(commands,0) );
1532+
1533+
/* Clean up */
1534+
pfree(buf.data);
1535+
/** returns list of create foreign table statements to run **/
1536+
return commands;
1537+
}
1538+
#endif /*end import foreign schema **/
1539+
1540+
static void strTableColumnLaunder (char *str)
1541+
{
1542+
int i, j = 0;
1543+
for(i = 0; str[i]; i++)
1544+
{
1545+
char c = tolower(str[i]);
1546+
1547+
/* First character is a numeral, prefix with 'n' */
1548+
if ( i == 0 && (c >= 48 && c <= 57) )
1549+
{
1550+
str[j++] = 'n';
1551+
}
1552+
1553+
/* Replace non-safe characters w/ _ */
1554+
if ( (c >= 48 && c <= 57) || /* 0-9 */
1555+
(c >= 65 && c <= 90) || /* A-Z */
1556+
(c >= 97 && c <= 122 ) /* a-z */ )
1557+
{
1558+
/* Good character, do nothing */
1559+
}
1560+
else
1561+
{
1562+
c = '_';
1563+
}
1564+
str[j++] = c;
1565+
1566+
/* Avoid mucking with data beyond the end of our stack-allocated strings */
1567+
if ( j >= STR_MAX_LEN )
1568+
j = STR_MAX_LEN - 1;
1569+
}
1570+
}

0 commit comments

Comments
 (0)