Back to VirtualPG home page
Updated documentation supporting VirtualPG v2
WarningThe following informations correspond to the most recent Version 2 of VirtualPG, and could not always adequately cover the previous versions, that are now declared obsolete.If you are still using any previous version you are warmly reccommended to quickly update your software. |
Short summary of differences between earlier versions and v2
|
about VirtualPG
VirtualPG 2 is available both as a classic C/C++ library and as a loadable dynamic extension module for both SQLite and SpatiaLite.The intended scope of VirtualPG is supporting direct data exchange form/to PostgreSQL / PostGIS targets and SQLite simply using basic, plain SQL statements.
VirtualPG as a standard C/C++ library
There is very little to say about VirtualPG as a library, because it implements just two C functions:- VIRTUALPG_DECLARE const char *virtualpg_version (void);
- this first API will return the current version of the library.
- VIRTUALPG_DECLARE int virtualpg_extension_init (sqlite3 *db_handle);
- this second API is intened to initialize the VirtualPG extension module:
- db_handle must be a valid handle to some already established SQLite connection.
- the return value will be SQLITE_OK if the extension module has been succesfully initialized.
- this second API is intened to initialize the VirtualPG extension module:
- the VirtualPG extension does not necessarily require to be supported by the SpatiaLite extension; they are independent the one from the other.
- the relative order in which the two extensions are eventually initialized is absolutely not relevant.
- however, when VirtualPG is backed up by SpatiaLite, you'll always be able to enquiry in full detail any error message raised by PostgreSQL by calling the SQL function PostgreSql_GetLastMessage().
this is a really useful diagnostic function, and will greatly improve the usability of VirtualPG, allowing for a simplified and easier debugging. - when SpatiaLite's support is unavailable, VirtualPG will simply attempt to output the error message coming from PostgreSQL on the standard error (if available).
- Note: the diagnostic SQL function PostgreSQL_GetLastError() requires using SpatiaLite version 5.0.0 or later.
a very skeletal C source sample
#include <stdlib.h> #include <stdio.h> #include <sqlite3.h> #include <virtualpg.h> int main (int argc, char *argv[]) { const char *path = ... some value .... sqlite3 *handle; virtualPQ virtual_api; int ret; /* opening the SQLite connection */ ret = sqlite3_open_v2 (path, &handle, SQLITE_OPEN_READWRITE, NULL); if (ret != SQLITE_OK) { ... error handling ... } /* initializing the PQ virtual API */ virtual_api.PQclear = ... virtual_api.PQconnectdb = ... ... /* initializing VirtualPG */ ret = virtualpg_extension_init (handle, &virtual_api); if (ret != SQLITE_OK) { ... error handling ... } else fprintf (stderr, "ok, VirtualPG succesfully initialized\n"); /* disconnecting from SQLite and exiting */ sqlite3_close (handle); return 0; }
about virtualPQ
libvirtualpg depends on the PostgreSQL client library (libpq) but you can freely choose between two alternative mechanisms for linking libpq:- hard-linking (aka early-binding): this is the most familiar one.
you just have to directly link -lpq to the application calling libvirtualpg; then your code, when calling virtualpg_extension_init(), will directly pass all PQ API pointers to the extension module.
This way your application will fully depend from the dynamic library libpq; if this should ever fail to be loaded for any reason your application will be completely unable to start. - soft-linking (aka late-binding): your application will always start without loading libpq; this dynamic library will be actually loaded only when really required.
If, for any reason, libpq should ever fail to be loaded your application will survive without negative effects (except than PostgreSQL integration will be obviously unavailable).
virtualPQ virtual_api; */ initialization of the virtual API based on hard-linked LibPQ */ virtual_api.PQclear = PQclear; virtual_api.PQconnectdb = PQconnectdb; virtual_api.PQerrorMessage = PQerrorMessage; virtual_api.PQexec = PQexec; virtual_api.PQfinish = PQfinish; virtual_api.PQgetisnull = PQgetisnull; virtual_api.PQgetvalue = PQgetvalue; virtual_api.PQlibVersion = PQlibVersion; virtual_api.PQnfields = PQnfields; virtual_api.PQntuples = PQntuples; virtual_api.PQresultStatus = PQresultStatus; virtual_api.PQstatus = PQstatus;The above code snippet exemplifies how-to initialize the Virtual API in the hard-linked scenario.
Note: in this case your application is expected to be linked by specifying (at least) -lvirtualpg -lsqlite3 -lpq.
virtualPQ virtual_api; wxDynamicLibrary DynamicLibPQ; */ initialization of the virtual API based on dynamically loaded LibPQ */ if (DynamicLibPQ.Load(path, wxDL_QUIET)) { bool ok; virtual_api.PQclear = (void (*)(PGresult *)) DynamicLibPQ.GetSymbol(wxT("PQclear"), &ok); virtual_api.PQconnectdb = (PGconn * (*)(const char *conninfo)) DynamicLibPQ.GetSymbol(wxT("PQconnectdb"), &ok); virtual_api.PQerrorMessage = (char *(*)(const PGconn * conn)) DynamicLibPQ.GetSymbol(wxT("PQerrorMessage"), &ok); virtual_api.PQexec = (PGresult * (*)(PGconn * conn, const char *command)) DynamicLibPQ.GetSymbol(wxT("PQexec"), &ok); virtual_api.PQfinish = (void (*)(PGconn * conn)) DynamicLibPQ.GetSymbol(wxT("PQfinish"), &ok); virtual_api.PQgetisnull = (int (*)(const PGresult * res, int row_number, int column_number)) DynamicLibPQ.GetSymbol(wxT("PQgetisnull"), &ok); virtual_api.PQgetvalue = (char *(*)(const PGresult * res, int row_number, int column_number)) DynamicLibPQ.GetSymbol(wxT("PQgetvalue"), &ok); virtual_api.PQlibVersion = (int (*)(void)) DynamicLibPQ.GetSymbol(wxT("PQlibVersion"), &ok); virtual_api.PQnfields = (int (*)(const PGresult * res)) DynamicLibPQ.GetSymbol(wxT("PQnfields"), &ok); virtual_api.PQntuples = (int (*)(const PGresult * res)) DynamicLibPQ.GetSymbol(wxT("PQntuples"), &ok); virtual_api.PQresultStatus = (ExecStatusType(*)(const PGresult * res)) DynamicLibPQ.GetSymbol(wxT("PQresultStatus"), &ok); virtual_api.PQstatus = (ConnStatusType(*)(const PGconn * conn)) DynamicLibPQ.GetSymbol(wxT("PQstatus"), &ok); }This second code snippet exemplifies how-to initialize the Virtual API in the late binding scenario.
The code is based on the cross-platform class wxDynamicLibrary supported by wxWidgets.
Linux natively supports dlopen() and dlsym(), whilst Windows supports its own LoadLibrary() and GetProcAddress(); all them are very similar in design, and the overall approach is almost the same.
Note: in this case your application is expected to be linked by specifying only -lvirtualpg -lsqlite3, but not -lpq.
VirtualPG as a dynamic extension module
The extension module simply is an ordinary dynamic library (.dll on Windows, .so on Unix and Linux, .dylib on Mac Os X); so the first thing to be done in order to activate this extension is loading the corresponding library from a valid SQLite/SpatiaLite connection.SELECT load_extension('mod_virtualpg'); ------ NULLNote:
- this mechanism just relies on the internal capabilities of SQLite, so it's expected to work on any possible language: Java, Python, PHP, C# and so on.
- all recent versions of SQLite are smart enough to not require specifying any platform-dependent suffix.
just passing mod_spatialite as the extension name should be enough on any correctly configured platform. - the VirtualPG extension does not necessarily require to be supported by the SpatiaLite extension; they are independent the one from the other.
- the relative order in which the two extensions are eventually loadeed is absolutely not relevant.
- however, when VirtualPG is backed up by SpatiaLite, you'll always be able to enquiry in full detail any error message raised by PostgreSQL by calling the SQL function PostgreSql_GetLastMessage().
this is a really useful diagnostic function, and will greatly improve the usability of VirtualPG, allowing for a simplified and easier debugging. - when SpatiaLite's support is unavailable, VirtualPG will simply attempt to output the error message coming from PostgreSQL on the standard error (if available).
- Note: the diagnostic SQL function PostgreSQL_GetLastError() requires using SpatiaLite version 5.0.0 or later.
Useful hints and suggestions: troubleshooting |
This operation could eventually fail for several different reasons: here are the most frequents:
SELECT load_extension('/usr/local/lib/mod_virtualpg.so', 'sqlite3_virtualpg_init'); ------ NULL |
Using VirtualPG: an SQL perspective
Creating VirtualPostgres tables
After successfully loading the virtualpg dynamic extension you are now ready to create VirtualPostges tables.These are quite almost ordinary DB tables as any other; you can execute any regular SQL statement (SELECT, INSERT, UPDATE or DELETE) by specifying these Virtual Tables as the selected target.
There is only a strong difference distinguishing a VirtualPostgres table; it's physical location isn't within the currently connected SQLite DB, but is instead within some PostgreSQL DBMS.
So a VirtualPostgres table effectively acts as a bridge joining SQLite and Postgres, and allowing to perform any kind of cross data exchange (in both directions) in the easiest way.
CREATE VIRTUAL TABLE pg_in USING VirtualPostgres ('host=localhost port=5432 dbname=gis user=sandro password=secret', my_schema, my_table);This SQL statement will create into the SQLite DB a table named pg_in wrapping the PostgreSQL table named my_schema.my_table.
Data for this table will continue to be stored within the Postgres own data space, but will be now accessible for SQLite:
- the first argument to VirtualPostgres is the connection string required in order to establish a connection to the Postgres DBMS server.
- second and third arguments respectively are the schema name and the table name identifying the Postgres target table.
CREATE VIRTUAL TABLE pg_in USING VirtualPostgres ('hostaddr=192.168.1.91 port=5432 dbname=gis user=sandro password=secret', my_schema, my_table);Same as above, this time establishing a connection to a Postgres instance running on some different computer on the same local area network.
CREATE VIRTUAL TABLE pg_in USING VirtualPostgres ('host=www.humptydumpty.com port=5432 dbname=gis user=sandro password=secret', my_schema, my_table);In this third variation we'll connect to a Postgres instance running on some remote server accessed though a wide area network.
Please consult the Postgres own documentation for any other detail about connection strings.
Creating VirtualPostgres tables: full syntax explained
CREATE VIRTUAL TABLE somename USING VirtualPostgres {conn_info, schema, table [ , read_write_flag [ , julian_flag ]] };Supported arguments:
- mandatory: conn_info (Text String)
any valid connection string accepted by PostgreSQL. For further details please consult the PostgreSQL own documentation. - mandatory: schema (Text String)
name of the PosgreSQL Schema containing the Table to be wrapped. - mandatory: table (Text String)
name of the PosgreSQL Table (or View) to be wrapped. - optional: read_write_flag (Text String):
- W: the Table will support both read and write operations, enabling SELECT / INSERT / UPDATE / DELETE statements.
- -: the Table will just support read operations: SELECT statements will be enabled, but INSERT / UPDATE / DELETE will be disabled.
Note: this is the default setting. VirtualPG always requires an explicit authorization in order to enable write operations.
- optional: julian_flag (Text String):
- J: DATE / TIME / TIMESTAMP values will be translated into the corresponding Julian Day Numbers (FLOAT values).
- -: DATE / TIME / TIMESTAMP values will be translated into the corresponding standard SQL Text Strings..
Note: this is the default setting. VirtualPG always requires an explicit authorization in order to enable Julian Days.
Useful hints: connecting to remote PostgreSQL istances via Port Forwarding / SSH Tunneling |
Whenever you require to connect some remote PostgreSQL istance (i.e. one actually running on some remote server requiring to establish a LAN or WAN connection) you should always seriously consider using SSH Tunneling / Port Forwarding. This could greatly simplify your approach, ensuring at the same time an uncompromised safety. Please read this Wiki page for more specific details about SSH Tunneling and Port Forwarding. |
Basic SQL operations
PRAGMA table_info(pg_in);You can execute the above PRAGMA directive in order to identify all columns declared by the Postgres table, and their corresponding data-type.
SELECT a, b, c, Count(*) AS cnt FROM pg_in WHERE e IS NOT NULL GROUP BY a, b, c ORDER BY cnt DESC;You can execute any kind of SELECT query using a VirtualPostgres table, exactly as if it was a genuine native table.
CREATE TABLE imported_from_pg AS SELECT * FROM pg_in;You can obviously create and populate a local copy of the remote Postgres table: now all data are permanently stored within your SQLite DB.
BEGIN; COMMIT; ROLLBACK;Transactions are honoured on the PostgreSQL side as well, when a VirtualPostgres Table supports INSERT / UPDATE / DELETE statements.
DROP TABLE pg_in;Finally, you can drop the VirtualPostgres table, and this will immediately terminate the connection to the Postgres DBMS.
data-type mapping and related conversions
Rather obviously SQLite and Postgres supports very different kind of data-types. The few following rules apply:- any kind of INTEGER value is preserved as such (this including BIGINT).
- the same is for FLOAT values.
- MONEY and NUMERIC values are converted into FLOAT values.
- CHAR and VARCHAR values are always converted into TEXT values.
- Handling of DATE, TIME and TIMESTAMP values depends on the specifc flag (J) declared in the CREATE VIRTUAL TABLE statement:
- as Text values
DATE, TIME and TIMESTAMP values are always converted into TEXT string accordingly to the standard SQL representations:- an example of DATE value: 2018-08-16
- an example of TIME value: 12:30:45
- an example of TIMESTAMP value: 2018-08-16 12:30:45
- as Julian Numbers
DATE, TIME and TIMESTAMP values are always converted into FLOAT values corresponding to the JulianDay notation supported by SQLite.
You can then invoke the appropriate function between Date(), Time() or DateTime() so to get back the standard SQL notation for dates and times. - Hint: using TEXT strings is more natural and make easier reading the date-time related values.
using JULIAN NUMBERS make simpler performing date-time related computations, as e.g. computing the elapsed interval (in days) between two dates.
- as Text values
- BOOL values are always converted into INTEGER values: 0 corresponding to FALSE.
- Postgres supports multiple values to be stored into the same column (ARRAY): SQLite has no similar capability, so any ARRAY value will simply be converted into a TEXT string, e.g. as in '{1,2,3}' or '{alpha,beta,gamma,delta}'.
- any binary value (as e.g. BYTEA) will be simply converted into a TEXT value containing the corresponding hexadecimal notation, e.g. as in '0101000020E8640000C4FC34BA8448244150C1B5D541B85341'.
handling PostGIS geometries
PostGIS own geometries simply are represented by a BYTEA value to be interpreted accordingly to EWKB format specifications.CREATE TABLE from_postgis AS SELECT * FROM pg_in; UPDATE from_postgis SET geometry = GeomFromEWKB(geometry); SELECT DISTINCT ST_GeometryType(geometry), ST_Srid(geometry), CoordDimension(geometry) FROM from_postgis; ------------ LINESTRING|25832|XY SELECT RecoverGeometryColumn('from_postgis', 'geometry', 25832, 'LINESTRING', 'XY'); ------------ 1This sequence of SQL statements (SpatiaLite support is strictly required) will transfer a fully qualified Geometry table from PostGIS to SpatiaLite:
- step 1: first we'll transfer the whole PostGIS table into SQLite; geometries are still represented by their EWKB hexadecimal notations.
- step 2: then we'll use the GeomFromEWKB() SQL function so to get the corresponding SpatiaLite's BLOB for each geometry.
- step 3: now we simply have to discover the Type, SRID and Dimensions declared by these geometries.
- step 4: and finally we can now recover a full qualified SpatiaLite's own (native) Geometry column.
exporting data from SQLite to Postgres/PostGIS
For rather obvious security reasons, all VirtualPostgres tables are declared to be read-only by default.This actually means that you are enabled to execute any possible SELECT query, but any INSERT, UPDATE or DELETE operation affecting the Postgres DBMS is strictly forbidden.
CREATE VIRTUAL TABLE pg_out USING VirtualPostgres ('hostaddr=192.168.1.91 port=5432 dbname=gis user=sandro password=secret', my_schema, my_table, W);A special W flag must be specified when creating a VirtualPostgres Table in order to allow for unconstrained read-write operations, this including any INSERT, UPDATE or DELETE statement affecting the Postgres DBMS.
BEGIN; DELETE FROM pg_out; INSERT INTO pg_out (fld_a, fld_b, fld_c, geometry) SELECT fld_a, fld_b, fld_c, AsEWKB(geometry) FROM some_table; COMMIT;In the above example we'll delete first any already existing row from the PostGIS table; then we'll simply transfer the whole table from SpatiaLite into PostGIS.
Please note, we'll invoke the AsEWKB() SQL function in order to convert any SpatiaLite's own BLOB geometry into the equivalent BYTEA EWKB notation required by PostGIS.
Note: starting a Transaction (BEGIN/COMMIT) on the PosgreSQL side always is a suggested good practice when writing many data on the PostgreSQL target, and will have a positive impact on overall performances.
Caveat: AsEWKB() |
All earlier versions of SpatiaLite released before 4.2.0 supported a buggish AsEWKB() implementation completely unable to create valid EWKB Geometries to be passed to PostGIS. Using version 4.2.0 (or any later) is strictly required so to really get a working implementation of AsEWKB(). |
Back to VirtualPG home page