diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bdfad7e9dc9..050870eaa0f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,11 @@ IF (WITH_SPATIALITE) ENDIF (WITH_INTERNAL_SPATIALITE) ENDIF (WITH_SPATIALITE) +SET (WITH_ORACLE FALSE CACHE BOOL "Determines whether Oracle support should be built") +IF(WITH_ORACLE) + SET(HAVE_ORACLE TRUE) +ENDIF(WITH_ORACLE) + # try to configure and build python bindings by default SET (WITH_BINDINGS TRUE CACHE BOOL "Determines whether python bindings should be built") IF (WITH_BINDINGS) diff --git a/cmake_templates/qgsconfig.h.in b/cmake_templates/qgsconfig.h.in index 185724bc6aad..7af2ffce13cf 100644 --- a/cmake_templates/qgsconfig.h.in +++ b/cmake_templates/qgsconfig.h.in @@ -38,6 +38,8 @@ #cmakedefine HAVE_MSSQL +#cmakedefine HAVE_ORACLE + #cmakedefine HAVE_PYTHON #cmakedefine HAVE_TOUCH diff --git a/debian/control.sid-oracle b/debian/control.sid-oracle new file mode 100644 index 000000000000..cd9252422213 --- /dev/null +++ b/debian/control.sid-oracle @@ -0,0 +1,232 @@ +Source: qgis +Section: science +Priority: extra +Maintainer: Quantum GIS developers +Build-Depends: + bison, + cmake (>= 2.6), + debhelper (>= 9), + flex, + grass-dev, + libexpat1-dev, + libfcgi-dev, + libgdal1-dev, + libgeos-dev (>= 3.0.0), + libgsl0-dev, + libpq-dev, + libproj-dev, + libqt4-dev (>=4.4.0), + libqt4-opengl-dev, + libqtwebkit-dev, + libqwt-dev, + libspatialite-dev, + libsqlite3-dev, + libspatialindex-dev, + pkg-config, + pyqt4-dev-tools, + python, + python-dev (>= 2.6.6-3~), + python-qt4 (>=4.1.0), + python-qt4-dev (>=4.1.0), + python-sip (>= 4.5.0), + python-sip-dev (>= 4.5.0), + libosgearth-dev, + libopenscenegraph-dev, + git, + doxygen, + graphviz, + txt2tags, + xvfb, xauth, xfonts-base, + oracle-instantclient11.2-devel +Build-Conflicts: libqgis-dev, qgis-dev +Standards-Version: 3.9.3 +X-Python-Version: current +Homepage: http://qgis.org/ + +Package: qgis +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, qgis-providers (= ${binary:Version}), qgis-common (= ${source:Version}) +Recommends: qgis-plugin-globe, qgis-plugin-grass, python-qgis +Suggests: gpsbabel +Conflicts: uim-qt3 +Description: Geographic Information System (GIS) + A Geographic Information System (GIS) manages, analyzes, and displays + databases of geographic information. Quantum GIS (QGIS) supports shape file + viewing and editing, spatial data storage with PostgreSQL/PostGIS, projection + on-the-fly, map composition, and a number of other features via a plugin + interface. QGIS also supports display of various georeferenced raster and + Digital Elevation Model (DEM) formats including GeoTIFF, Arc/Info ASCII Grid, + and USGS ASCII DEM. + +Package: qgis-common +Architecture: all +Depends: ${misc:Depends} +Description: Quantum GIS - architecture-independent data + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains architecture-independent supporting data files for use + with Quantum GIS. + +Package: libqgis{QGIS_ABI} +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Replaces: qgis (<=0.9.2rc1), libqgis-core1, libqgis-gui1, libqgis0, libqgis1 +Description: Quantum GIS - shared libraries + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains the shared core and gui library that provide an + interface for plugins and stand-alone applications. + +Package: libqgis-dev +Architecture: any +Section: libdevel +Depends: + grass-dev, + libexpat1-dev, + libgdal1-dev, + libgeos-dev (>= 3.0.0), + libgsl0-dev, + libpq-dev, + libproj-dev, + libqgis{QGIS_ABI} (= ${binary:Version}), + libqt4-dev (>=4.4.0), + libsqlite3-dev, + python-qt4 (>=4.1.0), + python-qt4-dev (>=4.1.0), + qt4-designer (>=4.4.0), + ${misc:Depends} +Provides: qgis-dev +Replaces: qgis-dev, libqgis1-dev, libqgis1.4.0-dev +Description: Quantum GIS - development files + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains the headers and libraries needed to develop plugins for + Quantum GIS. + +Package: qgis-plugin-grass +Architecture: any +Depends: qgis (= ${binary:Version}), qgis-plugin-grass-common (= ${source:Version}), ${shlibs:Depends}, ${misc:Depends}, grass{GRASS_ABI} +Description: GRASS plugin for Quantum GIS + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This plugin enables GRASS data access in the Quantum GIS geographic data + viewer. + +Package: qgis-plugin-grass-common +Architecture: all +Depends: python, ${misc:Depends} +Replaces: qgis-common (<< 1.5) +Breaks: qgis-common (<< 1.5) +Description: GRASS plugin for Quantum GIS - architecture-independent data + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains architecture-independent supporting data files for use + with the Quantum GIS GRASS plugin. + +Package: python-qgis +Section: python +Architecture: any +Depends: python-qt4 (>=4.1.0), python-sip (>= 4.5.0), python-qgis-common (= ${source:Version}), python-pyspatialite, python-psycopg2, python-qscintilla2, ${shlibs:Depends}, ${misc:Depends}, ${python:Depends} +Description: Python bindings to Quantum GIS + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains the files for the Python support. + +Package: python-qgis-common +Section: python +Architecture: all +Provides: ${python:Provides} +Depends: gdal-bin, python-gdal, libjs-jquery, libjs-underscore, ${misc:Depends} +Description: Python bindings to Quantum GIS - architecture-independent files + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains architecture-independent files for the Quantum GIS + Python bindings. + +Package: qgis-providers +Architecture: any +Depends: qgis-providers-common (= ${source:Version}), ${shlibs:Depends}, ${misc:Depends} +Replaces: qgis (<= 1.6) +Breaks: qgis (<= 1.6) +Description: collection of data providers to Quantum GIS + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains the provider plugins for Quantum GIS. + +Package: qgis-providers-common +Architecture: all +Replaces: qgis-common (<= 1.6) +Breaks: qgis-common (<= 1.6) +Depends: ${misc:Depends} +Description: collection of data providers to Quantum GIS - architecture-independent files + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains architecture-independent files for the Quantum GIS + providers. + +Package: qgis-mapserver +Architecture: any +Depends: qgis-providers (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends} +Description: Quantum GIS mapserver + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains the Quantum GIS mapserver. + +Package: qgis-sqlanywhere +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Conflicts: qgis-sqlanywhere1.7.0, qgis-sqlanywhere1.7.1, qgis-sqlanywhere1.8.0 +Description: Quantum GIS sql anywhere plugin and provider + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains the Quantum GIS sqlanywhere plugin and provider. + +Package: qgis-api-doc +Architecture: all +Section: doc +Depends: libjs-jquery +Description: Quantum GIS API documentation + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains the Quantum GIS API documentation. + +Package: qgis-plugin-globe +Architecture: any +Depends: qgis (= ${binary:Version}), qgis-plugin-globe-common (= ${source:Version}), ${shlibs:Depends}, ${misc:Depends} +Description: OSG globe plugin for Quantum GIS + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This plugin enables 3D viewing using OSG globe in the Quantum GIS. + +Package: qgis-plugin-globe-common +Architecture: all +Description: OSG GLOBE plugin for Quantum GIS - architecture-independent data + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains architecture-independent supporting data files for use + with the Quantum GIS GLOBE plugin. + +Package: qgis-oracle-provider +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Section: contrib/databases +Description: Quantum GIS oracle provider + Quantum GIS is a Geographic Information System (GIS) which manages, analyzes + and display databases of geographic information. + . + This package contains the Quantum GIS oracle provider. + diff --git a/debian/qgis-oracle-provider.install.in b/debian/qgis-oracle-provider.install.in new file mode 100644 index 000000000000..1841cd2c82fb --- /dev/null +++ b/debian/qgis-oracle-provider.install.in @@ -0,0 +1,2 @@ +usr/lib/qgis/plugins/liboracleprovider.so +usr/lib/{DEB_BUILD_GNU_TYPE}/qt4/plugins/sqldrivers/libqsqlocispatial.so diff --git a/debian/rules b/debian/rules index 611fc2bcfe1d..01b615ea703b 100755 --- a/debian/rules +++ b/debian/rules @@ -16,7 +16,7 @@ ifeq (,$(DISTRIBUTION)) DISTRIBUTION := $(shell dpkg-parsechangelog --format rfc822 | sed -ne "s/^Distribution: //p") endif -ifneq ($(DISTRIBUTION),$(findstring $(DISTRIBUTION),"squeeze wheezy lucid maverick natty oneiric precise")) +ifneq ($(DISTRIBUTION),$(findstring $(DISTRIBUTION),"squeeze wheezy lucid maverick natty oneiric precise sid-oracle")) DISTRIBUTION := sid endif @@ -71,6 +71,10 @@ else CMAKE_OPTS += -D WITH_GLOBE=TRUE endif +ifneq (,$(findstring -oracle,$(DISTRIBUTION))) + CMAKE_OPTS += -D WITH_ORACLE=TRUE +endif + ifneq (,$(findstring $(DISTRIBUTION),"wheezy sid")) CPPFLAGS := $(shell dpkg-buildflags --get CPPFLAGS) CFLAGS := $(shell dpkg-buildflags --get CFLAGS) $(CPPFLAGS) @@ -107,6 +111,7 @@ endif define gentemplate $(2): $(1) sed \ + -e "s/{DEB_BUILD_GNU_TYPE}/$(DEB_BUILD_GNU_TYPE)/g" \ -e "s/{QGIS_ABI}/$(QGIS_ABI)/g" \ -e "s/{GRASS}/$(GRASS)/g" \ -e "s/{GRASS_ABI}/$(GRASS_ABI)/g" $$^ >$$@ diff --git a/doc/TRANSLATORS b/doc/TRANSLATORS index b17d15ff5809..cd0e1ed910c7 100755 --- a/doc/TRANSLATORS +++ b/doc/TRANSLATORS @@ -1,46 +1,46 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/i18n/qgis_de.ts b/i18n/qgis_de.ts index c612d01d8226..3eb7b7b1440b 100644 --- a/i18n/qgis_de.ts +++ b/i18n/qgis_de.ts @@ -274,6 +274,10 @@ Append data to fileDaten an Datei anhängen + + Append data into file + Daten an Datei anhängen + Options @@ -282,12 +286,12 @@ Source SRID - Quell-SRID + Quell-SRID: Target SRID - Ziel-SRID + Ziel-SRID: @@ -385,6 +389,10 @@ Append data to table Daten an Tabellle anhängen + + Append data into table + An vorhandene anhängen + Options @@ -1856,6 +1864,65 @@ Soll sie dem Projekt als neuer Layer hinzugefügt werden? Liste leeren + + DlgAddGeometryColumn + + Dialog + Dialog + + + Name + Name + + + geom + geom + + + Type + Typ + + + POINT + Punkt + + + LINESTRING + Linie + + + POLYGON + Polygon + + + MULTIPOINT + Multipunkt + + + MULTILINESTRING + Multilinien + + + MULTIPOLYGON + Multipolygon + + + GEOMETRYCOLLECTION + Geometriesammlung + + + Dimensions + Dimensionen + + + SRID + SRID + + + -1 + -1 + + DlgConfig @@ -1879,6 +1946,173 @@ Soll sie dem Projekt als neuer Layer hinzugefügt werden? Wert + + DlgCreateConstraint + + Add constraint + Restriktion hinzufügen + + + Column + Spalte + + + Primary key + Primärschlüssel + + + Unique + Eindeutig + + + + DlgCreateIndex + + Create index + Index erzeugen + + + Column + Spalte + + + Name + Name + + + + DlgCreateTable + + Create Table + Tabelle erzeugen + + + Schema + Schema + + + Name + Name + + + Add field + Feld hinzufügen + + + Delete field + Feld löschen + + + Up + Auf + + + Down + Ab + + + Primary key + Primärschlüssel + + + Create geometry column + Geometriespalte hinzufügen + + + POINT + Punkt + + + LINESTRING + Linien + + + POLYGON + Polygon + + + MULTIPOINT + Multipunkt + + + MULTILINESTRING + Multilinie + + + MULTIPOLYGON + Multipolygon + + + GEOMETRYCOLLECTION + Geometriesammlung + + + geom + geom + + + Dimensions + Dimensionen + + + SRID + SRID + + + -1 + -1 + + + Create spatial index + Räumlichen Index erzeugen + + + + DlgDbError + + Database Error + Datenbank-Fehler + + + An error occured: + Ein Fehler ist aufgetreten: + + + An error occured when executing a query: + Ein Fehler trat bei der Ausführung folgender Abfrage auf: + + + Query: + Abfrage: + + + + DlgFieldProperties + + Field properties + Feldeigenschaften + + + Name + Name + + + Type + Typ + + + Can be NULL + Kann NULL sein + + + Default value + Vorgabewert + + + Length + Länge + + DlgHelpEdition @@ -1910,6 +2144,69 @@ Soll sie dem Projekt als neuer Layer hinzugefügt werden? Protokoll + + DlgImportVector + + Import vector layer + Vektorlayer importieren + + + Schema: + Schema: + + + Table: + Tabelle: + + + Action + Aktion + + + Create new table + Neue Tabelle erzeugen + + + Drop existing one + Vorhandene löschen + + + Append data into table + An vorhandene anhängen + + + Options + Optionen + + + Primary key: + Primärschlüssel: + + + Geometry column: + Geometriespalte: + + + Source SRID: + Quelle-SRID: + + + Target SRID: + Ziel-SRID: + + + Encoding: + Kodierung: + + + Create single-part geometries instead of multi-part + Einteilige statt mehrteiliger Geometrien erzeugen + + + Create spatial index + Räumlichen Index erzeugen + + DlgModeler @@ -1958,6 +2255,78 @@ Soll sie dem Projekt als neuer Layer hinzugefügt werden? DlgSqlWindow + + SQL window + SQL-Fenster + + + SQL query: + SQL-Abfrage: + + + &Execute (F5) + &Ausführen (F5) + + + F5 + F5 + + + &Clear + &Löschen + + + Result: + Ergebnis: + + + Load as new layer + Als neuen Layer laden + + + Column with unique +integer values + Spalte mit eindeutigen +ganzzahligen Werten + + + Geometry column + Geometriespalte + + + Retrieve +columns + Spalten +laden + + + Layer name (prefix) + Layername (Präfix) + + + Type + Typ + + + Vector + Vektor + + + Raster + Raster + + + Load now! + Jetzt laden! + + + <html><head/><body><p>Avoid selecting feature by id. Sometimes - especially when running expensive queries/views - fetching the data sequentially instead of fetching features by id can be much quicker.</p></body></html> + <html><head/><body><p>Auswahl von Objekten nach ID vermeiden. Manchmal- besonders, wenn teure Abfragen durchgeführt/Sichten abgefragt werden - kann das sequentielle Laden der Objekte deutlich schneller als die Abfrage nach ID sein.</p></body></html> + + + Avoid selecting by feature id + Objektabfrage nach ID vermeiden + Sorry Schade @@ -1969,6 +2338,73 @@ geometry column - column with unique integer values Geometriespate - Spalte mit eindeutigen ganzahligen Werden + + DlgTableProperties + + Table properties + Tabellleneigenschaften + + + Columns + Spalten + + + Table columns: + Tabellenspalten: + + + Add column + Spalte hinzufügen + + + Add geometry column + Geometriespalte hinzufügen + + + Edit column + Spalte bearbeiten + + + Delete column + Spalte löschen + + + Constraints + Restriktionen + + + Primary, foreign keys, unique and check constraints: + Primär-, Fremdschlüssel-, eindeutige und Prüfrestriktionen: + + + Add primary key / unique + Eindeutige/Primärschlüssel + + + Delete constraint + Restriktion löschen + + + Indexes + Indizes + + + Indexes defined for this table: + Für die Tabelle definierte Indizes: + + + Add index + Index hinzufügen + + + Add spatial index + Räumlichen Index hinzufügen + + + Delete index + Index löschen + + DlgVersioning @@ -2763,6 +3199,33 @@ Deaktivieren Sie die Option "Geschnittene Ausdehnung verwenden" um ein GDAL driver path GDAL-Treiberpfad + + A list of colon-separated (Linux and MacOS) or +semicolon-separated (Windows) paths to both binaries +and python executables. + +MacOS users usually need to set it to something like +/Library/Frameworks/GDAL.framework/Versions/1.8/Programs + Eine durch Doppelpunkte (Linux/MacOS) oder +Semikolons (Windows) unterteilte Pfadliste zu den +Programmen und Pythonscripts. +MacOS-Benutzer müssen dies normalerweise auf etwas +wie das folgende setzen: +/Library/Frameworks/GDAL.framework/Versions/1.8/Programs + + + A list of colon-separated (Linux and MacOS) or +semicolon-separated (Windows) paths to python modules. + Eine durch Doppelpunkte (Linux/MacOS) oder +Semikolons (Windows) unterteilte Pfadliste zu den +Pythonmodulen. + + + Useful to open local GDAL documentation instead of online help +when pressing on the tool dialog's Help button. + Nützlich um die lokale GDAL-Dokumentation statt der Onlinehilfe +über den Hilfeknopf des Werkzeugs aufzurufen. + A list of colon-separated (Linux and MacOS) or semicolon-separated (Windows) paths to both binaries @@ -4518,162 +4981,162 @@ GEOS-Verarbeitungsfehler: Eine oder mehrere Objekte haben ungültige Geometrie.< Layer koordinieren - + Digitizing Digitalisierung - + Advanced Digitizing Erweiterte Digitalisierung - + Map Navigation Kartennavigation - + Attributes Attribute - + Plugins Erweiterungen - + Help Hilfe - + Raster Raster - + Label Beschriftung - + &New Project &Neues Projekt - + Ctrl+N Strg+N - + &Open Project... Pr&ojekt öffnen... - + Ctrl+O Strg+O - + &Save Project Projekt &speichern - + Ctrl+S Strg+S - + Save Project &As... Projekt speichern &als... - + Ctrl+Shift+S Strg+Umschalt+S - + Save as Image... Bild speichern als... - + &New Print Composer &Neue Druckzusammenstellung - + Ctrl+P Strg+P - + Exit Beenden - + Ctrl+Q Strg+Q - + &Undo &Rückgängig - + Ctrl+Z Strg+Z - + &Redo &Wiederholen - + Ctrl+Shift+Z Strg+Umschalt+Z - + Cut Features Ausgewählte Objekte ausschneiden - + Ctrl+X Strg+X - + Copy Features Objekte kopieren - + Ctrl+C Strg+C - + Paste Features Objekte einfügen - + Ctrl+V Strg+V - + Ctrl+. Strg+. @@ -4688,477 +5151,482 @@ GEOS-Verarbeitungsfehler: Eine oder mehrere Objekte haben ungültige Geometrie.< Neues Projekt aus Vorlage - + Vector Vektor - + Database Datenbank - + Web Web - + Composer Manager... Druckzusammenstellungen verwalten... - + Add Feature Objekt hinzufügen - + Move Feature(s) Objekt(e) verschieben - + Reshape Features Objekte überarbeiten - + Split Features Objekte trennen - + Delete Selected Ausgewähltes löschen - + Add Ring Ring hinzufügen - + Add Part Teil hinzufügen - + Simplify Feature Objekt vereinfachen - + Delete Ring Ring löschen - + Delete Part Teil löschen - + Node Tool Knotenwerkzeug - + Rotate Point Symbols Punktsymbole drehen - + Snapping Options... Fangoptionen... - + Pan Map Karte verschieben - + Zoom In Hineinzoomen - + Ctrl++ Strg++ - + Zoom Out Hinauszoomen - + Ctrl+- Strg+- - + Identify Features Objekte abfragen - + Ctrl+Shift+I Strg+Umschalt+I - + Measure Line Linie messen - - + + Ctrl+Shift+M Strg+Umschalt+M - + Measure Area Fläche messen - + Ctrl+Shift+J Strg+Umschalt+J - + Measure Angle Winkel messen - + Zoom Full Volle Ausdehnung - + Ctrl+Shift+F Strg+Umschalt+F - + Zoom to Layer Auf den Layer zoomen - + Zoom to Selection Zur Auswahl zoomen - + Ctrl+J Strg+J - + Zoom Last Zoom zurück - + Zoom Next Zoom vor - + Zoom Actual Size Auf tatsächliche Größe zoomen - + Zoom to Native Pixel Resolution Auf normale Pixelauflösung zoomen - + Map Tips Kartenhinweise - + Show information about a feature when the mouse is hovered over it Informationen zu einem Objekt anzeigen, wenn die Maus darüber fährt - + New Bookmark... Neues Lesezeichen... - + Ctrl+B Strg+B - + Show Bookmarks Lesezeichen anzeigen - + Ctrl+Shift+B Strg+Umschalt+B - + Refresh Aktualisieren - + Ctrl+R Strg+R - + Text Annotation Beschriftungstext - + Move Annotation Beschriftung verschieben - + Labeling Beschriftung - + Layer Labeling Options Layerbeschriftungseinstellungen - + New Shapefile Layer... Neuer Shapedatei-Layer... - + Ctrl+Shift+N Strg+Umschalt+N - + New SpatiaLite Layer ... Neuer SpatiaLite-Layer... - + Ctrl+Shift+A Neuen SpatiaLite-Layer anlegen - + Raster calculator ... Rasterrechner ... - + Add Vector Layer... Vektorlayer hinzufügen... - + Ctrl+Shift+V Strg+Umschalt+V - + Add Raster Layer... Rasterlayer hinzufügen... - + Ctrl+Shift+R Strg+Umschalt+R - + Ctrl+Shift+D Strg+Umschalt+D - + Add SpatiaLite Layer... SpatiaLite-Layer hinzufügen... - + Ctrl+Shift+L Strg+Umschalt+L - + Add MSSQL Spatial Layer... Räumlichen MSSQL-Layer hinzufügen... - + + Add Oracle Spatial Layer... + Oracle Spatial-Layer hinzufügen... + + + Add WMS Layer... WMS-Layer hinzufügen... - + Ctrl+Shift+W Strg+Umschalt+W - + Open Attribute Table Attributtabelle öffnen - + Toggles the editing state of the current layer Bearbeitungsstatus des aktuellen Layers umschalten - + Save for Selected Layer(s) Speichern gewählter Layer - + Save edits to current layer, but continue editing Speichert Änderungen und bleibt im Bearbeitungsmodus - + Remove Layer(s) Layer löschen - + Ctrl+D Strg+D - + Set CRS of Layer(s) KBS von Layer(n) setzen - + Ctrl+Shift+C Strg+Umschalt+C - + Rotate Label Ctl (Cmd) increments by 15 deg. Beschriftung drehen Strg (Cmd) erhöht um 15 Grad. - + Style Manager... Stilmanager... - + Stretch Histogram to Full Dataset Histogramm auf den ganzen Datensatz strecken - + Embed Layers and Groups... Eingebettete Layer und Gruppen... - + &Copyright Label &Urheberrechtshinweis - + Creates a copyright label that is displayed on the map canvas. Erzeugt einen Urheberrechtshinweis auf dem Kartenbild. - + &North Arrow &Nordpfeil - + "Creates a north arrow that is displayed on the map canvas" "Erzeugt einen Nordpfeil und stellt ihn in der Karte dar" - + &Scale Bar &Maßstab - - + + Creates a scale bar that is displayed on the map canvas Erzeugt eine Maßstabsleiste, die im Kartenbild angezeigt wird - + Add WFS Layer... WFS-Layer hinzufügen... - + Add WFS Layer WFS-Layer hinzufügen - + Feature Action Objektaktion - - + + Pan Map to Selection Karte zur Auswahl verschieben - + Copy style Stil kopieren - + Paste style Stil einfügen - + Add WCS Layer... WCS-Layer hinzufügen... - + &Grid &Gitter - + Grid Gitter - + Pin/Unpin Labels Beschriftung anpinnen/lösen - + Pin/Unpin Labels Click or marquee on label to pin Shift unpins, Ctl (Cmd) toggles state @@ -5169,18 +5637,18 @@ Umschalt löst, Strg (Cmd) schalten um Funktioniert auf allen änderbaren Layern - - + + Highlight Pinned Labels Angepinnte Beschriftungen hervorheben - + Show/Hide Labels Beschriftungen anzeigen/ausblenden - + Show/Hide Labels Click or marquee on feature to show label Shift+click or marquee on label to hide it @@ -5190,369 +5658,382 @@ Objekt anklicken oder markieren, um die Beschriftung anzuzeigen Funktioniert auf allen ändernbaren Layern - - + + Html Annotation HTML-Beschriftung - - + + Duplicate Layer(s) Layer kopieren - + SVG annotation SVG-Anmerkung - - + + Save for All Layers Alle Layer speichern - - + + Rollback for All Layers Alle Layeränderungen verwerfen - - + + Cancel for All Layers Abbruch für alle Layer - - + + Rollback for Selected Layer(s) Verwerfen für gewählte Layer - - + + Current Edits Aktuelle Änderungen - - + + Cancel for Selected Layer(s) Abbruch für gewählte Layer - - + + Save Layer Edits Layeränderungen speichern - - + Save Edits for All Layers + Änderungen aller Layer speichern + + + Save edits for all layers, but continue editing + Änderungen aller Layer speichern, aber mit Bearbeitung fortfahren + + + + New Blank Project Neues leeres Projekt - + Local Cumulative Cut Stretch Lokale kommulative Schnittstreckung - + Local cumulative cut stretch using current extent, default limits and estimated values. Lokale kommulative Schnittstreckung mit aktueller Ausdehnung, Vorgabegrenzen und geschätzten Werten. - + Full Dataset Cumulative Cut Stretch Kommulative Schnittstreckung über Gesamtdatensatz - + Cumulative cut stretch using full dataset extent, default limits and estimated values. Kommulative Schnittstreckung über gesamten Datensatzausdehnung, Vorgabegrenzen und geschätzten Werten. - + Properties... Eigenschaften... - + Query... Abfrage... - + Add to Overview Zur Übersicht hinzufügen - + + Ctrl+Shift+O Strg+Umschalt+O - + Add All to Overview Alle zur Übersicht hinzufügen - + Merge Selected Features Gewählte Objekte verschmelzen - + Merge Attributes of Selected Features Attribute gewählter Objekte vereinen - + Select Single Feature Einzelnes Objekt wählen - + Select Features by Rectangle Objekte durch Rechteck wählen - + Select Features by Polygon Objekte durch Polygon wählen - + Select Features by Freehand Objekte freihändig wählen - + Select Features by Radius Objekte durch Radius wählen - + Deselect Features from All Layers Auswahlen aller Layer aufheben - + Form Annotation Beschriftungsformular - + Add PostGIS Layers... PostGIS-Layer hinzufügen... - + Toggle Editing Bearbeitungsstatus umschalten - + Save Edits + Änderungen speichern + + + Save As... Speichern als... - + Save Selection as Vector File... Auswahl als Vektordatei speichern... - + Set Project CRS from Layer Layer-KBS dem Projekt zuweisen - + Remove All from Overview Alle aus Übersicht entfernen - + Show All Layers Alle Layer anzeigen - + Ctrl+Shift+U Strg+Umschalt+U - + Hide All Layers Alle Layer ausblenden - + Ctrl+Shift+H Strg+Umschalt+H - + Manage Plugins... Erweiterungen verwalten... - + Toggle Full Screen Mode Auf Vollbildmodus schalten - + Ctrl+F Strg+F - + Project Properties... Projekteinstellungen... - + Ctrl+Shift+P Strg+Umschalt+P - + Options... Optionen... - + Custom CRS... Benutzerkoordinatenbezugssystem... - + Configure shortcuts... Tastenkürzel festlegen... - + Local Histogram Stretch Lokale Histogrammstreckung - + Stretch histogram of active raster to view extents Strecke das Histogram des aktiven Rasters um Ausdehnung zu zeigen - + Help Contents Hilfe-Übersicht - + F1 F1 - + API documentation API-Dokumentation - + QGIS Home Page QGIS-Homepage - + Ctrl+H Strg+H - + Check QGIS Version QGIS-Version überprüfen - + Check if your QGIS version is up to date (requires internet access) Aktualität Ihrer QGIS-Version überprüfen (erfordert Internetzugang) - + About Über - + QGIS Sponsors QGIS-Sponsoren - - + + Move Label Beschriftung verschieben - + Rotate Label Beschriftung drehen - + Change Label - + Run Feature Action Objektaktion ausführen - - + + Touch zoom and pan Zoomen und Verschieben durch Berührung - + Offset Curve Linie versetzen - + Python Console Python-Konsole - + Full histogram stretch Volle Histogrammstreckung - + Customization... Anpassungen... - + mActionCatchForCustomization mActionCatchForCustomization - + This is here just to avoid shortcut conflicts, the shortcut is caught in QgsCustomization Dies ist nur hier um widersprüchliche Kürzel zu vermeiden, das Kürzel wird in QgsCustomization abgefangen - + Ctrl+M Strg+M - + Embed layers and groups from other project files Eingebettete Layer und Gruppe aus anderen Projektdateien @@ -6731,29 +7212,103 @@ Bitte koorigieren Sie dies, da die OSM-Erweiterung nicht weiß welche Layer das QGis::UnitType - + meters Meter - + feet Fuß - - - - + + + + degrees Grad - + <unknown> <unbekannt> + + QOCISpatialDriver + + + Unable to initialize + QOCISpatialDriver + Initialisierung gescheitert + + + + Unable to logon + Login schlug fehl + + + + Unable to begin transaction + Transaktion konnt nicht gestartet werden + + + + Unable to commit transaction + Transaktion konnte nicht festgeschrieben werden + + + + Unable to rollback transaction + Transaktion konnte nicht zurückgerollt werden + + + + QOCISpatialResult + + + + + Unable to bind column for batch execute + Konnte Spalte nicht zur Stapelausführung binden + + + + Unable to execute batch statement + Konnte Stapelanweisung nicht ausführen + + + + Unable to goto next + Konnte nicht zum nächsten gehen + + + + Unable to alloc statement + Konnte Anweisung nicht reservieren + + + + Unable to prepare statement + Konnte Anweisung nicht vorbereiten + + + + Unable to get statement type + Konnte Anweisungstyp nicht bestimmen + + + + Unable to bind value + Konnte Wert nicht binden + + + + Unable to execute statement + Konnte Anweisung nicht ausführen + + QObject @@ -8244,19 +8799,19 @@ Fehler(%2): %3 - + Failed to transform a point while drawing a feature of type '%1'. Writing stopped. (Exception: %2) Transformation eines Punkts schlug beim Zeichnen eines Objekts vom Typ '%1' fehl. Schreiben beendet (Ausnahme %2) - + Feature write errors: Objektschreibfehler: - + Stopping after %1 errors Abbruch nach %1 Fehlern @@ -8425,17 +8980,17 @@ Nur %1 von %2 Objekten geschrieben. <html>QGIS bringt's!</html> - + CRS undefined - defaulting to project CRS KBS undefiniert - Projekt-KBS wird voreingestellt - + CRS undefined - defaulting to default CRS: %1 KBS undefiniert - voreingestelltes KBS gewählt: %1 - + Reading raster Lade Raster @@ -8865,37 +9420,44 @@ Diese Meldung erscheint höchstwahrscheinlich, weil die Umgebungsvariable DISPLA Daten auf einem 3D-Globus überlagern - + Unable to load %1 provider Kann Datenlieferant %1 nicht laden - + Provider %1 has no createEmptyLayer method Datenlieferant %1 kann keine leeren Layer erzeugen - + Loading of layer failed Laden des Layers gescheitert - + Creation error for features from #%1 to #%2. Provider errors was: %3 Objekterzeugungsfehler von #%1 bis #%2. Fehler des Datenlieferanten war: %3 - + + Import was canceled at %1 of %2 + Impot wurde bei %1 von %2 abgebrochen + + + Vector import Vektorimport - + Only %1 of %2 features written. Nur %1 von %2 Objekten geschrieben. + + @@ -8904,6 +9466,12 @@ Diese Meldung erscheint höchstwahrscheinlich, weil die Umgebungsvariable DISPLA Verbindung zur Datenbank schlug fehl + + No owner name found + Kein Besitzername gefunden + + + Creation of data source %1 failed: %2 @@ -8911,10 +9479,46 @@ Diese Meldung erscheint höchstwahrscheinlich, weil die Umgebungsvariable DISPLA %2 + Loading of the layer %1 failed Laden des Layers %1 gescheitert + + + Field name clash found (%1 not remappable) + Feldnamenkonflikt gefunden (%1 nicht abbildbar) + + + Fieldname clash found (%2 not remappable) + Feldnamenkonflikt gefunden (%1 nicht abbildbar) + + + + %1 not owner of the table %2. + %1 nicht Besitzer der Tabelle %2. + + + + Unable determine number of geometry columns of layer %1.%2: +%3 + Konnte die Geometriespaltenanzahl des Layer %1.%2 nicht feststellen: +%3 + + + + Unable to delete layer %1.%2: +%3 + Konnte Layer %1.%2 nicht löschen: +%3 + + + + Unable to clean metadata %1.%2: +%3 + Konnte Metadaten %1.%2 nicht löschen: +%3 + @@ -8930,12 +9534,14 @@ Diese Meldung erscheint höchstwahrscheinlich, weil die Umgebungsvariable DISPLA + Unsupported type for field %1 Nicht unterstützter Typ für Feld %1 + Creation of fields failed Erzeugung der Felder gescheitert @@ -9028,17 +9634,17 @@ Diese Meldung erscheint höchstwahrscheinlich, weil die Umgebungsvariable DISPLA - - - - - - - - - - - + + + + + + + + + + + Exception: %1 Ausnahme: %1 @@ -9054,23 +9660,23 @@ Diese Meldung erscheint höchstwahrscheinlich, weil die Umgebungsvariable DISPLA - - - - - - - - - - - - + + + + + + + + + + + + GEOS GEOS - + GEOS prior to 3.2 doesn't support GEOSInterpolate GEOS vor 3.2 unterstützt GEOSInterpolate nicht @@ -9244,450 +9850,450 @@ Diese Meldung erscheint höchstwahrscheinlich, weil die Umgebungsvariable DISPLA QgisApp - - + + Invalid Data Source Ungültige Datenquelle - - + + No Layer Selected Keinen Layer ausgewählt - + There is a new version of QGIS available Eine neue Version von QGIS ist verfügbar - + You are running a development version of QGIS Sie verwenden eine Entwicklungsversion von QGIS - + You are running the current version of QGIS Sie verwenden die aktuelle Version von QGIS - + Would you like more information? Wollen Sie mehr Information? - - - - + + + + QGIS Version Information QGIS-Versionsinformationen - + Unable to get current version information from server Kann Informationen zu aktuellen Version nicht vom Server holen - + Connection refused - server may be down Verbindung abgelehnt - Server vielleicht heruntergefahren - + QGIS server was not found QGIS-Server nicht gefunden - - - - + + + + Invalid Layer Ungültiger Layer - + %1 is an invalid layer and cannot be loaded. %1 ist ein ungültiger Layer und kann nicht geladen werden. - + Problem deleting features Problem beim Löschen der Objekte - + A problem occured during deletion of features Beim Löschen der Objekte ist ein Problem aufgetreten - + No Vector Layer Selected Es wurde kein Vektorlayer gewählt - + Deleting features only works on vector layers Löschen von Objekten ist nur von Vektorlayern möglich - + To delete features, you must select a vector layer in the legend Zum Löschen von Objekte zu muss ein Vektorlayer in der Legende gewählt werden - + Map legend that displays all the layers currently on the map canvas. Click on the check box to turn a layer on or off. Double click on a layer in the legend to customize its appearance and set other properties. Legende, die alle im Kartenfenster angezeigten Layer enthält. Bitte auf die Kontrollkästchen klicken, um einen Layer an- oder auszuschalten. Mit einem Doppelklick in der Legende kann die Erscheinung und sonstige Eigenschaften eines Layers festgelegt werden. - + Map overview canvas. This canvas can be used to display a locator map that shows the current extent of the map canvas. The current extent is shown as a red rectangle. Any layer on the map can be added to the overview canvas. Übersichtsfenster. Dieses Fenster kann benutzt werden um die momentane Ausdehnung des Kartenfensters darzustellen. Der momentane Ausschnitt ist als rotes Rechteck dargestellt. Jeder Layer in der Karte kann zum Übersichtsfenster hinzugefügt werden. - + Displays the current map scale Zeigt den momentanen Kartenmaßstab an - + Render Zeichnen - + When checked, the map layers are rendered in response to map navigation commands and other events. When not checked, no rendering is done. This allows you to add a large number of layers and symbolize them before rendering. Wenn angewählt, werden die Kartenlayer abhängig von der Bedienung der Navigationsinstrumente, gezeichnet. Anderenfalls werden die Layer nicht gezeichnet. Dies erlaubt es, eine große Layeranzahl hinzuzufügen und das Aussehen der Layer vor dem Zeichnen zu setzen. - + Choose a QGIS project file Eine QGIS-Projektdatei wählen - + Toggle map rendering Zeichnen der Karte einschalten - + Open a GDAL Supported Raster Data Source Öffnen einer GDAL-Rasterdatenquelle - + Choose a QGIS project file to open QGIS-Projektdatei zum Öffnen wählen - + Reading settings Lese Einstellungen - + Setting up the GUI Richte die Oberfläche ein - + Checking database Überprüfe die Datenbank - + Restoring loaded plugins Stelle die geladenen Erweiterungen wieder her - + Initializing file filters Initialisiere Dateifilter - + Restoring window state Stelle Fensterstatus wieder her - - + + QGIS Ready! QGIS ist startklar! - + Ready Fertig - + QGIS version QGIS-Version - + QGIS code revision QGIS-Codeversion - + Compiled against Qt Kompiliert gegen Qt - + Running against Qt Laufendes Qt - + GEOS Version GEOS-Version - + PostgreSQL Client Version PostgreSQL-Client-Version - + No support. Keine Unterstützung. - + SpatiaLite Version SpatiaLite-Version - + QWT Version QWT-Version - + This copy of QGIS writes debugging output. Diese QGIS-Kopie schreibt Debugausgaben. - + Unable to open project Kann das Projekt nicht öffnen - - + + Duplicate layer: Layer kopieren: - + %1 (duplication resulted in invalid layer) %1 (Kopieren führt zu ungültigem Layer) - + %1 (%2type unsupported) %1 (Typ %2 nicht unterstützt) - + Unknown network socket error: %1 Unbekannter Netzwerkfehler: %1 - - + + Layer is not valid Layer ist ungültig - + The layer is not a valid layer and can not be added to the map Der Layer ist ungültig und kann daher nicht zum Kartenfenster hinzugefügt werden - + Project has layer(s) in edit mode with unsaved edits, which will NOT be saved! Projekt hat Layer im Bearbeitungsmodus mit nicht gespeicherten Bearbeitungen, die NICHT gespeichert werden! - + Save? Speichern? - + Do you want to save the current project?%1 Wollen Sie das aktuelle Projekt speichern?%1 - + Current CRS: %1 (OTFR enabled) Aktuelles KBS: %1 (OTF-Reprojektion aktiv) - + Current CRS: %1 (OTFR disabled) Aktuelles KBS: %1 (OTF-Reprojektion aus) - + Extents: Ausdehnung: - - + + Error adding valid layer to map canvas Fehler beim Hinzufügen eines gültigen Layers zur Karte - - - + + + Raster layer Raster-Layer - + Unsupported Data Source Nicht unterstütztes Datenformat - - - - - - - - - - - + + + + + + + + + + + Error Fehler - + Checking provider plugins Provider-Erweiterungen werden geprüft - + Starting Python Python wird gestartet - + Provider does not support deletion Provider unterstützt keine Löschoperationen - + Data provider does not support deleting features Der Provider hat nicht die Möglichkeit, Objekte zu löschen - - - + + + Layer not editable Der Layer kann nicht bearbeitet werden - + The current layer is not editable. Choose 'Start editing' in the digitizing toolbar. Der aktuelle Layer kann nicht bearbeitet werden. Bitte 'Bearbeitungsstatus umschalten' aus der Digitalisierwerkzeugleiste wählen. - + Scale Maßstab - + Current map scale (formatted as x:y) Aktueller Kartenmaßstab (x:y formatiert) - + Map coordinates at mouse cursor position Kartenkoordinaten beim Mauszeiger - + Current map scale Aktueller Kartenmaßstab - + Project file is older Projektdatei ist älter - + <tt>Settings:Options:General</tt> Menu path to setting options <tt>Einstellungen:Optionen:Allgemein</tt> - + Warn me when opening a project file saved with an older version of QGIS Beim Öffnen einer Projektdatei, die mit einer älteren QGIS-Version erstellt wurde, warnen - + Overview Übersicht - + Progress bar that displays the status of rendering layers and other time-intensive operations Fortschrittsanzeige für das Zeichnen von Layern und andere zeitintensive Operationen - + Stop map rendering Zeichnen der Karte abbrechen - + Map canvas. This is where raster and vector layers are displayed when added to the map Kartenansicht. Hier werden Raster- und Vektorlayer angezeigt, wenn sie der Karte hinzugefügt werden - + Toggle extents and mouse position display Ausdehnungs- und Mauspositionsanzeige umschalten - + This icon shows whether on the fly coordinate reference system transformation is enabled or not. Click the icon to bring up the project properties dialog to alter this behaviour. Diese Icon zeigt an, ob On-The-Fly-Transformation des Koordinatenbezugssystem aktiv ist. Anklicken, um dies in den Projektionseigenschaften zu ändern. - + CRS status - Click to open coordinate reference system dialog KBS-Status - Klicken um den Dialog zum Koordinatenbezugssystem zu öffnen - + Maptips require an active layer Kartentipps erfordern einen aktuellen Layer - + Multiple Instances of QgisApp Mehrere QgisApp-Instanzen - + Multiple instances of Quantum GIS application object detected. Please contact the developers. @@ -9700,155 +10306,155 @@ Bitte nehmen Sie Kontakt zu den Entwicklern auf. Quantum GIS - + Minimize Minimieren - + Ctrl+M Minimize Window Strg+M - + Minimizes the active window to the dock Minimiert das aktive Fenster ins Dock - + Zoom Zoom - + Toggles between a predefined size and the window size set by the user Schaltet zwischen voreingestellter und vom Benutzer bestimmten Fenstergröße um - + Bring All to Front Alle in den Vordergrund bringen - + Bring forward all open windows Alle geöffneten Fenster vorholen - + Failed to open Python console: Konnte Python-Konsole nicht öffnen: - + Panels Bedienfelder - + Toolbars Werkzeugkästen - + &Database Da&tenbank - - + + Coordinate: Koordinate: - + Current map coordinate Aktuelle Kartenkoordinate - + Control rendering order Bestimmt Zeichenreihenfolge - + Layer order Layerreihenfolge - - - + + + Private qgis.db Benutzer qgis.db - + Could not open qgis.db Konnte qgis.db nicht öffnen - + Migration of private qgis.db failed. %1 Migration der Benutzer qgis.db schlug fehl. %1 - + Compiled against GDAL/OGR Kompiliert mit GDAL/OGR - + Running against GDAL/OGR Läuft mit GDAL/OGR - + %1 doesn't have any layers %1 hat keine Layer - + Select raster layers to add... Einzufügende Rasterlayer wählen... - + Cannot get MSSQL select dialog from provider. Konnte den MSSQL-Auswahldialog nicht vom Datenlieferanten holen. - - - + + + QGis files QGis-Dateien - + Labeling Beschriftung - + Cannot copy style: %1 Kann Stil nicht kopieren: %1 - + Cannot parse style: %1:%2:%3 Kann Stil nicht interpretieren: %1:%2:%3 - + Cannot read style: %1 Kann Stil nicht lesen: %1 - + Could not %1 changes to layer %2 Errors: %3 @@ -9859,95 +10465,95 @@ Fehler: %3 - + rollback verworfen - + cancel abgebrochen - + Save Speichern - - - + + + all Alle - + Rollback Verwerfen - + Cancel Abbrechen - + Current edits Aktuelle Änderungen - + %1 current changes for %2 layer(s)? Aktuelle Änderungen für %2 Layer %1? - + copy Kopie - + Plugin layer Erweiterungslayer - + Memory layer Speicherlayer - + Couldn't load Python support library: %1 Konnte Python-Unterstützungsbibliothek nicht laden: %1 - + Couldn't resolve python support library's instance() symbol. Konnte Symbol instance() nicht in Python-Unterstützungsbibliothek finden. - + Python support ENABLED :-) Python-Unterstützung aktiviert :-) - + QGIS - Changes since last release QGIS-Änderung seit der letzten Ausgabe - - + + To perform a full histogram stretch, you need to have a raster layer selected. Um eine volle Histogrammstreckung durchzuführen, muß ein Rasterlayer gewählt sein. - + This project file was saved by an older version of QGIS Die Projektdatei wurde mit einer älteren QGIS-Version gespeichert - + Always ignore these errors? @@ -9956,7 +10562,7 @@ Always ignore these errors? Diese Fehler immer ignorieren? - + %n SSL errors occured number of errors @@ -9965,367 +10571,377 @@ Diese Fehler immer ignorieren? - + Raster Raster - + Security warning: Sicherheitswarnung: - + Log Messages Protokoll - + QGIS starting... QGIS startet... - + Current Edits Aktuelle Änderungen - + Window Fenster - + Vect&or &Vektor - + &Web &Web - + Shows the map coordinates at the current cursor position. The display is continuously updated as the mouse is moved. It also allows editing to set the canvas center to a given position. The format is lat,lon or east,north Zeigt die Kartenkoordinate an der aktuellen Mausposition. Die Anzeige wird laufend aktualisiert während die Maus bewegt wird. Sie kann auch bearbeitet werden, um die Kartenanzeige auf eine gegebene Koordinate zu zentrieren. Das Format ist Breite,Höhe oder Ost,Nord - + Current map coordinate (lat,lon or east,north) Aktuelle Kartenkoordinaten (Breite,Höhe oder Ost,Nord) - + Map layer list that displays all layers in drawing order. Layerliste, die alle Layer in Zeichenreihenfolge anzeigt. - + [ERROR] Can not make qgis.db private copy [FEHLER] Kann private Kopie von qgis.db nicht anlegen - + Update of view in private qgis.db failed. %1 Aktualisierung der Sicht in privater qgis.db gescheitert. %1 - - + + < Blank > < Leer > - + PROJ.4 Version PROJ.4-Version - + QScintilla2 Version QScintilla2-Version - + Select zip layers to add... ZIP-Layer zum Hinzufügen wählen... - + Vector Vektor - + Select vector layers to add... Einzufügende Vektorlayer wählen... - + PostgreSQL PostgreSQL - + Cannot get PostgreSQL select dialog from provider. Kann PostgreSQL-Auswahldialog des Datenlieferanten nicht bestimmen. - + %1 is an invalid layer - not loaded %1 ist ein ungültiger Layer - nicht geladen - + SpatiaLite SpatiaLite - + Cannot get SpatiaLite select dialog from provider. Kann SpatiaLite-Auswahldialog nicht vom Datenlieferanten holen. - + MSSQL MSSQL - + + Oracle + Oracle + + + + Cannot get Oracle select dialog from provider. + Konnte den Oracle-Auswahldialog nicht vom Datenlieferanten holen. + + + WMS WMS - + Cannot get WMS select dialog from provider. Konnte den WMS-Auswahldialog nicht vom Datenlieferanten holen. - + WCS WCS - + Cannot get WCS select dialog from provider. Konnte den WCS-Auswahldialog nicht vom Datenlieferanten holen. - + WFS WFS - + Cannot get WFS select dialog from provider. Konnte WFS-Auswahldialog nicht vom Datenlieferanten holen. - + Calculating... Berechne... - - + + Abort... Abbrechen... - + project macros have been disabled. Projektmakros wurden abgeschaltet. - + Enable macros Makros aktivieren - + Choose a file name to save the QGIS project file as Name für zu speichernden QGIS-Projektdatei wählen - + Unable to load %1 %1 kann nicht geladen werden - + Choose a file name to save the map image as Name für Datei zum Speichern des Kartenabbilds wählen - + Please select a vector layer first. Bitte wählen zur zuvor einen Layer. - + Layer labeling settings Layerbeschriftungseinstellungen - + Cannot write raster error code: %1 Rasterschreibfehlercode: %1 - + Saving done Speichern abgeschlossen - + Export to vector file has been completed Export in Vektordatei ist abgeschlossen - + Save error Fehler beim Speichern - + Export to vector file failed. Error: %1 Export in Vektordatei schlug fehl. Fehler: %1 - + Features deleted Objekt gelöscht - + Merging features... Objekte werden verschmolzen... - + Abort Abbrechen - - + + Composer %1 Druckzusammenstellung %1 - - + + No active layer Kein aktiver Layer - - + + No active layer found. Please select a layer in the layer list Keinen aktiven Layer gefunden. Bitte einen Layer aus der Liste wählen - - + + Active layer is not vector Aktiver ist kein Vektorlayer - - + + The merge features tool only works on vector layers. Please select a vector layer from the layer list Das Verschmelzen von Objekte funktioniert nur mit Vektorlayern. Bitte einen Vektorlayer aus der Liste wählen - - + + Merging features can only be done for layers in editing mode. To use the merge tool, go to Layer->Toggle editing Objekte können nur auf Layern im Bearbeitungsmodus verschmolzen werden. Bitte mit Layer->Bearbeitungsmodus umschalten, um das Verschmelzungswerkzeug zu benutzen - - - + + + The merge tool requires at least two selected features Das Verschmelzungswerkzeug erfordert mindestens zwei gewählte Objekte - - - + + + Not enough features selected Nicht genug Objekte gewählt - + Merged feature attributes Objektattribute vereinen - - + + Merge failed Zusammenführung fehlgeschlagen - - + + An error occured during the merge operation Beim Zusammenführen trat ein Fehler auf - - + + The union operation would result in a geometry type that is not compatible with the current layer and therefore is canceled Die Vereinigungsoperation würde zu einem Geometrietyp führen, der nicht zum aktuellen Layer paßt, und wurde daher abgebrochen - + Union operation canceled Vereinigungsvorgang abgebrochen - + Merged features Objekte verschmelzen - + Features cut Objekte ausgeschnitten - + Features pasted Objekte eingefügt - + Start editing failed Bearbeitungsbeginn schlug fehl - + Provider cannot be opened for editing Lieferant kann nicht zum Bearbeiten geöffnet werden - + Stop editing Bearbeitung beenden - + Do you want to save the changes to layer %1? Sollen die Änderungen am Layer %1 gespeichert werden? - - + + Could not commit changes to layer %1 Errors: %2 @@ -10336,91 +10952,91 @@ Fehler: %2 - + Problems during roll back Probleme beim Zurücknehmen der Änderungen - + GPS Information GPS-Information - + Map coordinates for the current view extents Kartenkoordinaten für den aktuell sichtbaren Ausschnitt - + Warning Warnung - + This layer doesn't have a properties dialog. Dieser Layer hat keine Eigenschaftendialog. - + Authentication required Authentifikation erforderlich - + Proxy authentication required Proxy-Authentifikation erforderlich - + SSL errors occured accessing URL %1: SSL-Fehler beim Zugriff auf URL %1: - + Quantum GIS - %1 ('%2') Quantum GIS - %1 ('%2') - + %1 is not a valid or recognized data source %1 ist keine gültige Datenquelle oder wird nicht erkannt - - + + Saved project to: %1 Projekt in %1 gespeichert - - + + Unable to save project %1 Konnte Projekt %1 nicht speichern - + Saved map image to %1 Kartenabbild als %1 gespeichert - + Unable to communicate with QGIS Version server %1 Konnte nicht mit dem QGIS-Versionserver kommunizieren %1 - + No Raster Layer Selected Kein Rasterlayer gewählt - + The layer %1 is not a valid layer and can not be added to the map Der Layer %1 ist ungültig und kann der Karte nicht hinzugefügt werden - + %n feature(s) selected on layer %1. number of selected features @@ -10429,27 +11045,27 @@ Fehler: %2 - + %1 is not a supported raster data source %1 ist keine unterstützte Rasterdatenquelle - + <p>This project file was saved by an older version of QGIS. When saving this project file, QGIS will update it to the latest version, possibly rendering it useless for older versions of QGIS.<p>Even though QGIS developers try to maintain backwards compatibility, some of the information from the old project file might be lost. To improve the quality of QGIS, we appreciate if you file a bug report at %3. Be sure to include the old project file, and state the version of QGIS you used to discover the error.<p>To remove this warning when opening an older project file, uncheck the box '%5' in the %4 menu.<p>Version of the project file: %1<br>Current version of QGIS: %2 <p>Diese Projektdatei wurde mit einer älteren QGIS-Version gespeichert. Beim Speichern dieser Projektdatei wird QGIS es auf die aktuelle Version aktualisieren und sie damit unter Umständen für ältere QGIS-Versionen unbrauchbar machen. <p>Obwohl die QGIS-Entwickler versuchen Rückwärtskompatibilität zu erhalten, könnten dabei einige Informationen der alten Projektdatei verloren gehen. Um die Qualität von QGIS zu verbessern, würden wir es begrüßen, wenn Sie einen Fehler unter %3 melden würden. Bitte legen Sie die alte Projektdatei bei und nennen Sie die QGIS-Version mit der Sie diesen Fehler entdeckt haben. <p>Um diese Warnung in Zukunft zu unterdrücken, entfernen Sie bitte das Häkchen in '%5' im Menü %4.<p>Version der Projektdatei: %1<br>Aktuelle QGIS-Version: %2 - + Layers Layer - + Delete features Objekte löschen - + Delete %n feature(s)? number of features to delete @@ -23851,6 +24467,10 @@ Die könnte auf ein Netzwerkproblem oder ein Problem des WMS-Server hindeuten.View feature form Objektformular anzeigen + + Print + Drucken + Clear results @@ -25684,92 +26304,102 @@ Die könnte auf ein Netzwerkproblem oder ein Problem des WMS-Server hindeuten.Sie sollten mindestens eine Verbindung aus der Liste wählen. - + Saving connections Verbindungen speichern - + Cannot write file %1: %2. Datei %1 kann nicht geschrieben werden: %2. - - - - - + + + + - - + + + - - - - - + + + + + + + + Loading connections Verbindungen laden - - + + Cannot read file %1: %2. Datei %1 kann nicht gelesen werden: %2. - - + + Parse error at line %1, column %2: %3 Fehler in Zeile %1, Spalte %2: %3 - + The file is not an WMS connections exchange file. Die Datei ist keine WMS-Verbindungsaustauschdatei. - - + + The file is not an WFS connections exchange file. Die Datei ist keine WFS-Verbindungsaustauschdatei. - + The file is not an WCS connections exchange file. Diese Datei ist keine WCS-Austauschdatei. - - - + + The file is not an PostGIS connections exchange file. Die Datei ist keine PostGIS-Verbindungsaustauschdatei. - + + The file is not an MSSQL connections exchange file. Die Datei ist keine MSSQL-Verbindungsaustauschdatei. - + + + The file is not an Oracle connections exchange file. + Die Datei ist keine Oracle-Verbindungsaustauschdatei. + + + The file is not an %1 connections exchange file. Die Datei ist keine %1-Verbindungsaustauschdatei. - - - - + + + + + Connection with name '%1' already exists. Overwrite? Verbindung names '%1' existiert bereits. Ersetzen? @@ -26239,6 +26869,10 @@ Die könnte auf ein Netzwerkproblem oder ein Problem des WMS-Server hindeuten. QgsMapToolChangeLabelProperties + + Label properties changed + Beschriftungseigenschaften geändert + Changed properties for label @@ -26419,6 +27053,10 @@ Die könnte auf ein Netzwerkproblem oder ein Problem des WMS-Server hindeuten. QgsMapToolMoveLabel + + Label moved + Beschriftung verschoben + Moved label @@ -26466,6 +27104,14 @@ Die könnte auf ein Netzwerkproblem oder ein Problem des WMS-Server hindeuten. QgsMapToolPinLabels + + Label pinned + Beschriftungen angepinnt + + + Label unpinned + Beschriftung gelöst + Pinned label @@ -26517,6 +27163,10 @@ Die könnte auf ein Netzwerkproblem oder ein Problem des WMS-Server hindeuten. QgsMapToolRotateLabel + + Label rotated + Beschriftung gedreht + Rotated label @@ -26553,6 +27203,14 @@ Die könnte auf ein Netzwerkproblem oder ein Problem des WMS-Server hindeuten. QgsMapToolShowHideLabels + + Label hidden + Beschriftungen ausgeblendet + + + Label shown + Beschriftung angezeigt + Hid label @@ -30092,6 +30750,10 @@ Immer Netzwerk: immer aus dem Netzwerk laden und nicht prüfen, ob im Cache ein Edit Create Options Erzeugungsoptionen bearbeiten + + Plugins + Erweiterungen + Plugin paths @@ -30113,6 +30775,10 @@ Immer Netzwerk: immer aus dem Netzwerk laden und nicht prüfen, ob im Cache ein Rendering behavior Zeichenverhalten + + Enable back buffer (Better graphics performance at the cost of loosing the possibility to cancel rendering and incremental feature drawing) + Hintergrundpuffer aktivieren (Bessere Grafikleistung unter Verzicht auf die Möglichkeit des Abbruchs des Zeichnens und des inkrementellen Zeichnen) + Number of features to draw before updating the display @@ -30671,6 +31337,370 @@ Immer Netzwerk: immer aus dem Netzwerk laden und nicht prüfen, ob im Cache ein Pixel + + QgsOracleConn + + + Connection to database failed + Verbindung zur Datenbank schlug fehl + + + + + + + + + + + Oracle + Oracle + + + + + SQL:%1 +error:%2 + + SQL:%1 +Fehler:%2 + + + + + Querying available tables failed. +SQL:%1 +error:%2 + + Abfrage der verfügbaren Tabelle schlug fehl. +SQL:%1 +Fehler:%2 + + + + + Database connection was successful, but the accessible tables could not be determined. + Die Datenbankverbindung war erfolgreich, jedoch konnten die zugänglichen Tabellen nicht bestimmt werden. + + + + Unable to get list of spatially enabled tables from the database + Konnte Liste der räumlichen Tabellen der Datenbank nicht bestimmen + + + + Unsupported geometry type %1 in %2.%3.%4 ignored + Nicht unterstützter Geometrietyp %1 in %2.%3.%4 ignoriert + + + + View %1.%2 doesn't have integer columns for use as keys. + Sicht %1.%2 hat keine als Schlüssel verwendbare Integer-Spalte. + + + + Point + Punkt + + + + Multipoint + Multipunkt + + + + Line + Linie + + + + Multiline + Multilinie + + + + Polygon + Polygon + + + + Multipolygon + Multipolygon + + + + No Geometry + Ohne Geometrie + + + + Unknown Geometry + Unbekannte Geometrie + + + + QgsOracleConnectionItem + + + Failed to retrieve layers + Konnte Layer nicht bestimmen + + + + No layers found. + Keine Layer gefunden. + + + + Edit... + Bearbeiten... + + + + Delete + Löschen + + + + Copying features... + Kopiere Objekte... + + + + Abort + Abbrechen + + + + Import layer + Layer importieren + + + + %1: Not a vector layer! + %1: Kein Vektorlayer! + + + + + %1: OK! + %1: OK! + + + + + Import to Oracle database + In Oracle-Datenbank importieren + + + + Failed to import some layers! + + + Konnte einige Layer nicht importieren! + + + + + + Import was successful. + Import war erfolgreich. + + + + QgsOracleLayerItem + + + + + Delete layer + Layer löschen + + + + Layer deleted successfully. + Layer erfolgreich gelöscht. + + + + QgsOracleNewConnection + + + Saving passwords + Paßworte speichern + + + + WARNING: You have opted to save your password. It will be stored in plain text in your project files and in your home directory on Unix-like systems, or in your user profile on Windows. If you do not want this to happen, please press the Cancel button. + + WARNUNG: Sie haben zugestimmt Ihr Paßwort zu speichern. Es wird im Klartext in Ihren Projektdateien, auf Unixsystemen in Ihrem Heimatverzeichnis und unter Windows in Ihrem Benutzerprofil gespeichert. Wenn Sie dies nicht wünschen, brechen Sie den Vorgang bitte ab. + + + + Save connection + Verbindung speichern + + + + Should the existing connection %1 be overwritten? + Soll die bereits existierende Verbindung %1 überschrieben werden? + + + + + Test connection + Verbindung testen + + + + Connection to %1 was successful + Verbindung zu %1 war erfolgreich + + + + Connection failed - Check settings and try again. + + + Verbindung gescheitert - Einstellungen überprüfen und wiederholen. + + + + + + QgsOracleNewConnectionBase + + + Create a New Oracle connection + Neue Oracle-Verbindung erstellen + + + + Connection Information + Verbindungsinformationen + + + + Password + Passwort + + + + Save Username + Benutzernamen speichern + + + + Username + Benutzername + + + + Name of the new connection + Name der neuen Verbindung + + + + Database + Datenbank + + + + Name + Name + + + + Restrict the displayed tables to those that are in the geometry_columns table + Beschränke angezeigte Tabellen auf jene aus der Layerregistratur + + + + <html><head/><body><p>Restricts the displayed tables to those that are in the all_sdo_geom_metadata view. This can speed up the initial display of spatial tables.</p></body></html> + <html><head/><body><p>Schränkt die angezeigten Tabelle auf die in der Sicht all_sdo_geom_metadata enthaltenen ein. Dies kann die anfängliche Anzeige der räumlichen Tabellen beschleunigen.</p></body></html> + + + + Only look in meta data table + Nur in der Metadatentabelle suchen + + + + Restrict the search to the public schema for spatial tables not in the geometry_columns table + Suche wird auf Tabellen beschränkt, die dem angemeldeten Benutzer gehören + + + + <html><head/><body><p>When searching for spatial tables restrict the search to tables that are owner by the user.</p></body></html> + <html><head/><body><p>Bei der Suche nach räumlichen Tabellen nur die berücksichtigen, die dem angemeldeten Benutzer gehören.</p></body></html> + + + <html><head/><body><p>When searching for spatial tables that are not in the all_sdo_geom_metadata view, restrict the search to tables that are owner by the user.</p></body></html> + <html><head/><body><p>Bei der Suche nach räumlichen Tabellen nur die berücksichtigen, die dem angemeldeten Benutzer gehören.</p></body></html> + + + + Only look for user's tables + Nur Tabellen des Benutzers berücksichtigen + + + + Also list tables with no geometry + Auch geometrielose Tabelle anzeigen + + + + Port + Port + + + + 1521 + 1521 + + + + &Test Connect + Verbindung &testen + + + + Save Password + Passwort speichern + + + + Host + Host + + + + Use estimated table statistics for the layer metadata. + Geschätzte Tabellenstatistik für die Tabellen-Metadaten verwenden. + + + + <html><head/><body><p>When the layer is setup various metadata is required for the Oracle table. This includes information such as the table row count, geometry type and spatial extents of the data in the geometry column. If the table contains a large number of rows determining this metadata is time consuming.</p><p>By activating this option the following fast table metadata operations are done:</p><p>1) Row count is determined from all_tables.num_rows.</p><p>2) Table extents are always determined with the SDO_TUNE.EXTENTS_OF function even if a layer filter is applied.</p><p>3) The table geometry is determined from the first 100 non-null geometry rows in the table.</p></body></html> + <html><head/><body><p>Zur Layereinbindung werden einige Metadaten zur Oracle-Tabelle benötigt. Dies schließt Informationen zur Zeilenanzahl, Geometrietyp und räumliche Ausdehnung ein. Wenn die Tabelle viele Zeilen enthält, kann dies sehr lange dauern.</p><p>Durch Aktivierung dieser Option werden die folgenden schnellen Operationen ausgeführt:</p><p>1) Zeilenanzahl wird aus all_tables.rows bestimmt.</p><p>2) Die Tabellenausmasse werden mit SDO_TUNE.EXTENTS_OF bestimmt, auch wenn ein Layerfilter vorliegt.</p><p>3) Der Tabellengeometrietyp und SRID wird aus den ersten 100 Zeilen bestimmt.</p></body></html> + + + + Use estimated table metadata + Geschätzte Tabellenmetadaten nutzen + + + + QgsOracleOwnerItem + + + %1 as %2 in %3 + %1 als %2 in %3 + + + + as geometryless table + als geometrielose Tabelle + + QgsOraclePlugin @@ -30684,6 +31714,425 @@ Immer Netzwerk: immer aus dem Netzwerk laden und nicht prüfen, ob im Cache ein Ein Oracle-Spatial-GeoRaster hinzufügen... + + QgsOracleProvider + + + Whole number + Ganzzahl + + + + Whole big number + Große ganze Zahl + + + + Decimal number (numeric) + Dezimalzahl (numeric) + + + + Decimal number (decimal) + Dezimalzahl (decimal) + + + + Decimal number (real) + Dezimalzahl (real) + + + + Decimal number (double) + Dezimalzahl (double) + + + + Text, fixed length (char) + Text, feste Länge (char) + + + + Text, limited variable length (varchar2) + Text, begrenzte variabler Länge (varchar2) + + + + Text, unlimited length (long) + Text, unbegrenzter Länge (long) + + + + Fetching features failed. +SQL:%1 +Error: %2 + Objektladen schlug fehl. +SQL: %1 +Fehler:%2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Oracle + Oracle + + + + Read attempt on an invalid oracle data source + Leseversuch auf ungültige Oracle-Datenquelle + + + + feature %1 not found + Objekt %1 nicht gefunden + + + + found more features instead of just one. + Mehr Objekte als nur eins gefunden. + + + + FAILURE: Field %1 not found. + FEHLER: Feld %1 nicht gefunden. + + + + Loading comment for table %1.%2 failed [%3] + Ladens des Tabellekommentars zu %1.%2 gescheitert [%3] + + + + Loading comment for columns of table %1.%2 failed [%3] + Ladens des Spaltenkommentar zu %1.%2 gescheitert [%3] + + + + Other Spatial field %1.%2.%3 ignored + Weiteres räumliches Feld %1.%2.%3 ignoriert + + + + Loading field types for table %1.%2 failed [%3] + Laden der Feldtypen der Tabelle %1.%2 fehlgeschlagen [%3] + + + + Invalid spatial index %1 on column %2.%3.%4 found - expect poor performance. + Ungültiger räumlicher Index %1 auf Spalte %2.%3.%4 gefunden - schlechte Performance ist zu erwarten. + + + + No spatial index on column %1.%2.%3 found - expect poor performance. + Kein räumlicher Index auf Spalte %1.%2.%3 gefunden - schlechte Performance ist zu erwarten. + + + + Probing for spatial index on column %1.%2.%3 failed [%4] + Suche nach räumlichen Index auf Spalte %1.%2.%3 fehlgeschlagen [%4] + + + + Retrieving fields from '%1' failed [%2] + Laden der Felder aus '%1' gescheitert [%2] + + + + Unable to determine geometry column access privileges for column %1.%2. +The error message from the database was: +%3. +SQL: %4 + Konnte die Zugriffsrechte auf die Geometriespalte %1.%2 nicht bestimmen. +Der Fehler der Datenbank war: +%3. +SQL: %4 + + + + Unable to determine table access privileges for the table %1. +The error message from the database was: +%2. +SQL: %3 + Konnte die Zugriffsrechte der Tabelle für die Beziehung %1 nicht bestimmen. +Der Fehler der Datenbank war: +%2. +SQL: %3 + + + + The custom query is not a select query. + Die Benutzerabfrage ist keine SELECT-Abfrage. + + + + + + + + + + Unable to execute the query. +The error message from the database was: +%1. +SQL: %2 + Konnte die Abfrage nicht ausführen. +Dei Fehlermeldung der Datenbank war: +%1. +SQL: %2 + + + + Primary key field %1 not found in %2 + Primärschlüsselfeld %1 nicht in %2 gefunden + + + + Primary key field '%1' for view not unique. + Primärschlüsselfeld '%1' der Sicht ist nicht eindeutig. + + + + Type '%1' of primary key field '%2' for view invalid. + Typ '%1' des Primärschlüsselfelds '%2' der Sicht ist ungültig. + + + + Key field '%1' for view not found. + Schlüsselfeld '%1' der Sicht nicht gefunden. + + + + No key field for view given. + Kein Schlüsselfeld für Ansicht angegeben. + + + + No key field for query given. + Kein Schlüsselfeld für Abfrage gegeben. + + + + + + + + + + Could not start transaction + Transaktion konnte nicht gestartet werden + + + + + + + + + + Could not commit transaction + Transaktion konnte nicht festgeschrieben werden + + + + Oracle error while adding features: %1 + Oracle-Fehler beim Attributhinzufügen: %1 + + + + + + + + + + Could not rollback transaction + Transaktion konnte nicht zurückgerollt werden + + + + Oracle error while deleting features: %1 + Oracle-Fehler beim Objektlöschen: %1 + + + + Oracle error while adding attributes: %1 + Oracle-Fehler beim Attributhinzufügen: %1 + + + + Oracle error while deleting attributes: %1 + Oracle-Fehler beim Attributlöschen: %1 + + + + Oracle error while changing attributes: %1 + Oracle-Fehler beim Attributändern: %1 + + + + Oracle error while changing geometry values: %1 + Oracle-Fehler beim Geometrieändern: %1 + + + + Could not retrieve extents: %1 +SQL: %2 + Konnte Ausmaße nicht bestimmen: %1 +SQL: %2 + + + + Could not execute query. +The error message from the database was: +%1. +SQL: %2 + Konnte Abfrage nicht ausführen. +Die Datenbankfehlermeldung war: +%2. +SQL: %2 + + + + Could not retrieve SRID of %1. +The error message from the database was: +%2. +SQL: %3 + Konnte SRID von %1 nicht bestimmen. +Die Datenbankfehlermeldung war: +%2. +SQL: %3 + + + + Could not determine SRID of %1. +The error message from the database was: +%2. +SQL: %3 + Konnte SRID von %1 nicht bestimmen. +Die Datenbankfehlermeldung war: +%2. +SQL: %3 + + + + Could not retrieve geometry type of %1. +The error message from the database was: +%2. +SQL: %3 + Konnte Geometrietyp von %1 nicht bestimmen. +Die Datenbankfehlermeldung war: +%2. +SQL: %3 + + + + Could not determine geometry type of %1. +The error message from the database was: +%2. +SQL: %3 + Konnte Geometrietyp von %1 nicht bestimmen. +Die Datenbankfehlermeldung war: +%2. +SQL: %3 + + + + Geometry type and srid for empty column %1 of %2 undefined. + Geometrietyp und SRID für leere Spalte %1 in %2 undefiniert. + + + + Feature type or srid for %1 of %2 could not be determined or was not requested. + Objekttyp oder SRID für %1 aus %2 konnte nicht festgestellt werden und wurde nicht festgelegt. + + + + Editing and adding disabled for 2D+ layer (%1; %2) + Ändern und Hinzufügen auf 2D+-Layern abgeschaltet (%1; %2) + + + + Creation spatial index failed. +SQL:%1 +Error: %2 + Erzeugung des räumlichen Index gescheitert. +SQL:%1 +FehleR:%2 + + + + Rebuild of spatial index failed. +SQL:%1 +Error: %2 + Neuaufbau des räumlichen Index gescheitert. +SQL:%1 +Fehler:%2 + + + + QgsOracleRootItem + + + New Connection... + Neue Verbindung... + + QgsOracleSelectGeoraster @@ -30717,6 +32166,206 @@ Immer Netzwerk: immer aus dem Netzwerk laden und nicht prüfen, ob im Cache ein Die Verbindung zu %1 schlug fehl. Bitte überprüfen Sie die Verbindungsparameter und stellen Sie sicher, dass die GDAL-Georaster-Erweiterung installiert ist. + + QgsOracleSourceSelect + + + Add Oracle Table(s) + Oracle-Tabelle(n) hinzufügen + + + + &Add + &Hinzufügen + + + + &Build query + &Abfrage erstellen + + + + Build query + Abfrage erstellen + + + + + Wildcard + Platzhalter + + + + + RegExp + RegAusdr + + + + + All + Alle + + + + + Owner + Besitzer + + + + + Table + Tabelle + + + + + Type + Typ + + + + + Geometry column + Geometriespalte + + + + + Primary key column + Primärschlüsselspalte + + + + + SRID + SRID + + + + + Sql + SQL + + + + Are you sure you want to remove the %1 connection and all associated settings? + Sind Sie sicher, dass Sie die Verbindung %1 und alle zugehörigen Einstellungen löschen wollen? + + + + Confirm Delete + Löschen bestätigen + + + + Load connections + Verbindungen laden + + + + XML files (*.xml *XML) + XML-Dateien (*.xml *.XML) + + + + Select Table + Tabelle wählen + + + + You must select a table in order to add a layer. + Um einen Layer hinzuzufügen, muss eine Tabelle gewählt sein. + + + + Stop + Stopp + + + + Oracle Locator Provider + Oracle-Locator-Datenlieferant + + + + Could not open the Oracle Locator Provider + Konnte Oracle-Locator-Datenlieferant nicht öffnen + + + + Connect + Verbinden + + + + QgsOracleSourceSelectDelegate + + + Select... + Wählen... + + + + QgsOracleTableModel + + + Owner + Besitzer + + + + Table + Tabelle + + + + Type + Typ + + + + Geometry column + Geometriespalte + + + + SRID + SRID + + + + Primary key column + Primärschlüsselspalte + + + + Select at id + Abfrage nach ID + + + + Sql + SQL + + + + + Select... + Wählen... + + + + Enter... + Eingeben... + + + + Disable 'Fast Access to Features at ID' capability to force keeping the attribute table in memory (e.g. in case of expensive views). + Funktion 'Schneller Objektzugriff nach ID' abschalten, damit die Attributtabelle im Speicher gehalten wird (z.B. bei teuren Ansichten). + + QgsPGConnectionItem @@ -34200,6 +35849,22 @@ p, li { white-space: pre-wrap; } QgsRasterDataProvider + + Identify + Abfragen + + + Build Pyramids + Pyramiden erzeugen + + + Create Datasources + Datenquellen erzeugen + + + Remove Datasources + Datenquellen entfernen + no data @@ -34210,6 +35875,10 @@ p, li { white-space: pre-wrap; } Feature info Objektinformation + + Band + Kanal + Average @@ -35681,6 +37350,10 @@ datasets with maximum width and height specified below. Vertical Vertikal + + Tiles + Kacheln + @@ -37220,6 +38893,10 @@ Der Fehler war: Classes Klassen + + Colors + Farben + Min @@ -37250,6 +38927,10 @@ Der Fehler war: Min / Max origin Min- / Max-Ursprung + + Invert colors order + Farbreihenfolge umkehren + Classify @@ -38821,6 +40502,56 @@ p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">Wir arbeiten wirklich hart, um diese Software für Sie zu erstellen. Haben Sie all die coolen Funktionen gesehen? Wird Ihnen wohlig warm, wenn Sie damit arbeiten? Quantum GIS ist das Produkt eines Teams das sich ihm aus Spaß and Freude verschrieben hat. Wir möchten dass Sie es kopieren und weitergeben und es so vielen Leute wie möglich zugänglich machen. Wenn Sie mit QGIS Geld sparen oder Sie unsere Arbeit gut finden und die Möglichkeit haben uns finanziell zu unterstützen, überlegen Sie sich bitte, ob Sie die Entwicklung von Quantum GIS nicht vielleicht auch unterstützen wollen. Wir verwenden das Geld unserer Sponsoren um Reise- und sonstige Kosten unserer halbjährlichen Entwicklertreffen und allgemeine Kosten zu decken. Nähere Details finden Sie auf der </span><a href="http://qgis.org/de/sponsoring.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;">QGIS-Sponsoren-Webseite</span></a><span style=" font-family:'Ubuntu';">. Auf der </span><a href="http://qgis.org/en/sponsorship/sponsors.html"><span style=" text-decoration: underline; color:#0000ff;">Sponsoren-Seite</span></a><span style=" font-family:'Ubuntu'; font-size:10pt;"> sehen Sie die guten Menschen und Firmen, die uns finanziell helfen - ein ganz großes Dankeschön ihnen allen!</span></p></body></html> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">We work really hard to make this nice software for you. See all the cool features it has? Get a warm fuzzy feeling when you use it? Quantum GIS is a labour of love by a dedicated team of developers. We want you to copy &amp; share it and put it in the hands of as many people as possible. If QGIS is saving you money or you like our work and have the financial ability to help, please consider sponsoring the development of Quantum GIS. We use money from sponsors to pay for travel and costs related to our bi-annual hackfests, and to generally support the goals of our project. Please see the </span><a href="http://qgis.org/en/sponsorship.html"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">QGIS Sponsorship Web Page</span></a><span style=" font-size:10pt;"> for more details. In the list below you can see the fine people and companies that are helping us financially - a great big 'thank you' to you all!</span></p> +<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:14pt; font-weight:600;">2011 Sponsors</span></p> +<hr /> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;">SILVER SPONSORS</span></p> +<p align="center" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.vorarlberg.at"><span style=" font-family:'Courier New,courier'; font-size:10pt; text-decoration: underline; color:#0000ff;">State of Vorarlberg</span></a><span style=" font-family:'Courier New,courier'; font-size:10pt;"> </span><span style=" font-family:'Courier New,courier'; font-size:10pt; color:#333333;">, Austria (11.2011)</span></p> +<p align="center" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.agi.so.ch"><span style=" font-family:'Courier New,courier'; font-size:10pt; text-decoration: underline; color:#0000ff;">Kanton Solothurn</span></a><span style=" font-family:'Courier New,courier'; font-size:10pt; color:#333333;">, Switzerland (4.2011)</span></p> +<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">BRONZE SPONSORS</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt; font-weight:600;"></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://gis.uster.ch/"><span style=" font-family:'Helvetica,Arial,sans-serif'; font-size:10pt; text-decoration: underline; color:#0000ff;">City of Uster</span></a><span style=" font-family:'Helvetica,Arial,sans-serif'; font-size:10pt; color:#0000ff;"> </span><span style=" font-family:'Helvetica,Arial,sans-serif'; font-size:10pt; color:#000000;">, Switzerland</span><span style=" font-family:'Helvetica,Arial,sans-serif'; color:#000000;"> (11.2011)</span></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.municipia.pt"><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline; color:#0000ff;">Municípia, SA</span></a></p> +<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:14pt; font-weight:600;">2010 Sponsors</span></p> +<hr /> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">BRONZE SPONSORS</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt; font-weight:600;"></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.gfosservices.com"><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline; color:#0000ff;">Studio Associato Gfosservices</span></a></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://nextgis.org"><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline; color:#0000ff;">NEXTGIS</span></a></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:14pt; font-weight:600;"></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//DE" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu';">Wir arbeiten wirklich hart, um diese Software für Sie zu erstellen. Haben Sie all die coolen Funktionen gesehen? Wird Ihnen wohlig warm, wenn Sie damit arbeiten? Quantum GIS ist das Produkt eines Teams das sich ihm aus Spaß and Freude verschrieben hat. Wir möchten dass Sie es kopieren und weitergeben und es so vielen Leute wie möglich zugänglich machen. Wenn Sie mit QGIS Geld sparen oder Sie unsere Arbeit gut finden und die Möglichkeit haben uns finanziell zu unterstützen, überlegen Sie sich bitte, ob Sie die Entwicklung von Quantum GIS nicht vielleicht auch unterstützen wollen. Wir verwenden das Geld unserer Sponsoren um Reise- und sonstige Kosten unserer halbjährlichen Entwicklertreffen und allgemeine Kosten zu decken. Nähere Details finden Sie auf der </span><a href="http://qgis.org/de/sponsoring.html"><span style=" font-family:'Ubuntu'; text-decoration: underline; color:#0000ff;">QGIS-Sponsoren-Webseite</span></a><span style=" font-family:'Ubuntu';">. +<p>Untenstehend finden Sie eine Liste der netten Menschen und Firmen die uns finanziell helfen - ein ganz großes Dankeschön Ihnen allen!</span></p> +<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu';"></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:14pt; font-weight:600;">2011 Sponsoren</span></p> +<hr /> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">SILVER SPONSORS</span></p> +<p align="center" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><p align="center" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.vorarlberg.at"><span style=" font-family:'Courier New,courier'; font-size:10pt; text-decoration: underline; color:#0000ff;">Bundesland Vorarlberg</span></a><span style=" font-family:'Courier New,courier'; font-size:10pt;"> </span><span style=" font-family:'Courier New,courier'; font-size:10pt; color:#333333;">, Österreich (11.2011)</span></p> +<p align="center" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><p align="center" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.agi.so.ch"><span style=" font-family:'Courier New,courier'; font-size:10pt; text-decoration: underline; color:#0000ff;">Kanton Solothurn</span></a><span style=" font-family:'Courier New,courier'; font-size:10pt; color:#333333;">, Schweiz (4.2011)</span></p> +<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-weight:600;">BRONZE SPONSOREN</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-weight:600;"></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://gis.uster.ch/"><span style=" font-family:'Helvetica,Arial,sans-serif'; font-size:10pt; text-decoration: underline; color:#0000ff;">Stadt Uster</span></a><span style=" font-family:'Helvetica,Arial,sans-serif'; font-size:10pt; color:#0000ff;"> </span><span style=" font-family:'Helvetica,Arial,sans-serif'; font-size:10pt; color:#000000;">, Schweiz</span><span style=" font-family:'Helvetica,Arial,sans-serif'; color:#000000;"> (11.2011)</span></p><p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.municipia.pt"><span style=" text-decoration: underline; color:#0000ff;">Municípia, SA</span></a></p> +<p align="center" style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu';"></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-size:14pt; font-weight:600;">2010 Sponsors</span></p> +<hr /> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Ubuntu'; font-weight:600;">BRONZE SPONSOREN</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-weight:600;"></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.gfosservices.com"><span style=" text-decoration: underline; color:#0000ff;">Studio Associato Gfosservices</span></a></p> +<p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://nextgis.org"><span style=" text-decoration: underline; color:#0000ff;">NEXTGIS</span></a></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Ubuntu'; font-size:14pt; font-weight:600;"></p></body></html> + QgsSqlAnywhereProvider @@ -42071,6 +43802,10 @@ Antwort war: Map request error (Status: %1; Response: %2; URL:%3) Kartenabfrage-Fehler (Status: %1; Antwort:%2; URL: %3) + + Cannot find boundary in multipart content type + Konnte Begrenzungen in Multipart-Content-Type nicht finden + Map request error (Response: %1; URL:%2) diff --git a/images/images.qrc b/images/images.qrc index 3ce1d17c384f..f8d0aad4e94b 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -202,6 +202,7 @@ themes/default/mIconLock.png themes/default/mIconNew.png themes/default/mIconMssql.png + themes/default/mIconOracle.png themes/default/mIconNext.png themes/default/mIconNoPyramid.png themes/default/mIconOws.png @@ -433,10 +434,12 @@ themes/gis/mIconRaster.png themes/gis/mIconPostgis.png themes/gis/mIconMssql.png + themes/gis/mIconOracle.png themes/gis/mIconConnect.png themes/gis/mIconDbSchema.png themes/gis/mActionTouch.png themes/default/mActionAddMssqlLayer.png + themes/default/mActionAddOracleLayer.png themes/default/mActionTouch.png themes/default/mActionTouch2.png themes/classic/mActionTouch.png diff --git a/images/themes/default/mActionAddOracleLayer.png b/images/themes/default/mActionAddOracleLayer.png new file mode 100644 index 000000000000..5c0c36472853 Binary files /dev/null and b/images/themes/default/mActionAddOracleLayer.png differ diff --git a/images/themes/default/mIconOracle.png b/images/themes/default/mIconOracle.png new file mode 100644 index 000000000000..0ad8e9927185 Binary files /dev/null and b/images/themes/default/mIconOracle.png differ diff --git a/images/themes/gis/mIconOracle.png b/images/themes/gis/mIconOracle.png new file mode 100644 index 000000000000..0ad8e9927185 Binary files /dev/null and b/images/themes/gis/mIconOracle.png differ diff --git a/ms-windows/osgeo4w/package-nightly.cmd b/ms-windows/osgeo4w/package-nightly.cmd index 2584a4284ac2..cf130b54f0dd 100644 --- a/ms-windows/osgeo4w/package-nightly.cmd +++ b/ms-windows/osgeo4w/package-nightly.cmd @@ -111,6 +111,7 @@ cmake -G "Visual Studio 9 2008" ^ -D WITH_ASTYLE=TRUE ^ -D WITH_GLOBE=TRUE ^ -D WITH_TOUCH=TRUE ^ + -D WITH_ORACLE=TRUE ^ -D CMAKE_CONFIGURATION_TYPES=%BUILDCONF% ^ -D GEOS_LIBRARY=%O4W_ROOT%/lib/geos_c_i.lib ^ -D SQLITE3_LIBRARY=%O4W_ROOT%/lib/sqlite3_i.lib ^ @@ -183,6 +184,7 @@ tar -C %OSGEO4W_ROOT% -cjf %PACKAGENAME%-%VERSION%-%PACKAGE%.tar.bz2 ^ bin/%PACKAGENAME%-browser.exe ^ bin/%PACKAGENAME%.bat.tmpl ^ bin/%PACKAGENAME%-browser.bat.tmpl ^ + apps/qt4/plugins/sqldrivers/qsqlocispatial.dll ^ etc/postinstall/%PACKAGENAME%.bat ^ etc/preremove/%PACKAGENAME%.bat ^ >>%LOG% 2>&1 diff --git a/ms-windows/osgeo4w/package.cmd b/ms-windows/osgeo4w/package.cmd index 6ee91bb8a1e5..7fad373447b3 100644 --- a/ms-windows/osgeo4w/package.cmd +++ b/ms-windows/osgeo4w/package.cmd @@ -107,6 +107,7 @@ cmake -G "Visual Studio 9 2008" ^ -D WITH_MAPSERVER=TRUE ^ -D WITH_GLOBE=TRUE ^ -D WITH_TOUCH=TRUE ^ + -D WITH_ORACLE=TRUE ^ -D CMAKE_BUILD_TYPE=%BUILDCONF% ^ -D CMAKE_CONFIGURATION_TYPES=%BUILDCONF% ^ -D GEOS_LIBRARY=%O4W_ROOT%/lib/geos_c_i.lib ^ @@ -281,6 +282,12 @@ tar -C %OSGEO4W_ROOT% -cjf %PACKAGENAME%-globe-plugin-%VERSION%-%PACKAGE%.tar.bz >>%LOG% 2>&1 if errorlevel 1 goto error +tar -C %OSGEO4W_ROOT% -cjf %PACKAGENAME%-oracle-provider-%VERSION%-%PACKAGE%.tar.bz2 ^ + "apps/%PACKAGENAME%/plugins/oracleprovider.dll" ^ + apps/qt4/plugins/sqldrivers/qsqlocispatial.dll ^ + >>%LOG% 2>&1 +if errorlevel 1 goto error + tar -C %OSGEO4W_ROOT% -cjf %PACKAGENAME%-devel-%VERSION%-%PACKAGE%.tar.bz2 ^ --exclude-from exclude ^ --exclude "*.pyc" ^ @@ -301,6 +308,7 @@ if exist %PACKAGENAME%-server-%VERSION%-%PACKAGE%.tar.bz2 del %PACKAGENAME%-serv if exist %PACKAGENAME%-devel-%VERSION%-%PACKAGE%.tar.bz2 del %PACKAGENAME%-devel-%VERSION%-%PACKAGE%.tar.bz2 if exist %PACKAGENAME%-grass-plugin-%VERSION%-%PACKAGE%.tar.bz2 del %PACKAGENAME%-grass-plugin-%VERSION%-%PACKAGE%.tar.bz2 if exist %PACKAGENAME%-globe-plugin-%VERSION%-%PACKAGE%.tar.bz2 del %PACKAGENAME%-globe-plugin-%VERSION%-%PACKAGE%.tar.bz2 +if exist %PACKAGENAME%-oracle-provider-%VERSION%-%PACKAGE%.tar.bz2 del %PACKAGENAME%-oracle-provider-%VERSION%-%PACKAGE%.tar.bz2 :end echo FINISHED: %DATE% %TIME% >>%LOG% 2>&1 diff --git a/scripts/addcopyright.sh b/scripts/addcopyright.sh index a376fe05973d..1c9f94a39dc7 100755 --- a/scripts/addcopyright.sh +++ b/scripts/addcopyright.sh @@ -18,7 +18,16 @@ set -e -for i in $(&2 author= authordate= diff --git a/scripts/licenses b/scripts/licenses new file mode 100644 index 000000000000..a05918c98b27 --- /dev/null +++ b/scripts/licenses @@ -0,0 +1,12 @@ +: GPL \(v[23] or later\)$ +: GPL \(v2 or later\) GENERATED FILE$ +: \*No copyright\* LGPL \(v2\.1 or later\)$ +: GPL LGPL$ +: LGPL$ +: Apache \(v2\.0\)$ +: GPL \(v2\)$ +: LGPL \(v2 or later\)$ +: Apache \(v2.0\) GPL \(v2 or later\)$ +: MIT\/X11 \(BSD like\)$ +: MPL \(v1\.1\) GPL \(unversioned\/unknown version\)$ +: LGPL \(v2\.1\)$ diff --git a/scripts/prepare-commit.sh b/scripts/prepare-commit.sh index 79afcee5b2a5..cb26c8575778 100755 --- a/scripts/prepare-commit.sh +++ b/scripts/prepare-commit.sh @@ -29,6 +29,11 @@ if ! type -p colordiff >/dev/null; then } fi +if [ "$1" = "-c" ]; then + echo "Cleaning..." + find . \( -name "*.prepare" -o -name "*.astyle" -o -name "astyle.*.diff" -o -name "sha-*.diff" \) -print -delete +fi + set -e # determine changed files diff --git a/src/app/main.cpp b/src/app/main.cpp index bedbfde930a2..023aaee6eaf4 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -200,8 +200,8 @@ void myMessageOutput( QtMsgType type, const char *msg ) case QtFatalMsg: { fprintf( stderr, "Fatal: %s\n", msg ); -#if defined(linux) && ! defined(ANDROID) - fprintf( stderr, "Stacktrace (run through c++filt):\n" ); +#if defined(linux) && !defined(ANDROID) + write( STDERR_FILENO, "Stacktrace (run through c++filt):\n", 34 ); void *buffer[256]; int nptrs = backtrace( buffer, sizeof( buffer ) / sizeof( *buffer ) ); backtrace_symbols_fd( buffer, nptrs, STDERR_FILENO ); diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 9572f40b63c2..0c6398e17618 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -382,9 +382,9 @@ void QgisApp::validateSrs( QgsCoordinateReferenceSystem &srs ) mySelector->setSelectedCrsId( defaultCrs.srsid() ); } - // why is this: it overrides the default cursor in the splitter in the dialog - // commenting it now till somebody tells us why it is necessary :-) - //QApplication::setOverrideCursor( Qt::ArrowCursor ); + bool waiting = QApplication::overrideCursor()->shape() == Qt::WaitCursor; + if ( waiting ) + QApplication::setOverrideCursor( Qt::ArrowCursor ); if ( mySelector->exec() ) { @@ -393,7 +393,8 @@ void QgisApp::validateSrs( QgsCoordinateReferenceSystem &srs ) srs.createFromOgcWmsCrs( mySelector->selectedAuthId() ); } - //QApplication::restoreOverrideCursor(); + if ( waiting ) + QApplication::restoreOverrideCursor(); delete mySelector; } @@ -944,6 +945,7 @@ void QgisApp::createActions() connect( mActionAddPgLayer, SIGNAL( triggered() ), this, SLOT( addDatabaseLayer() ) ); connect( mActionAddSpatiaLiteLayer, SIGNAL( triggered() ), this, SLOT( addSpatiaLiteLayer() ) ); connect( mActionAddMssqlLayer, SIGNAL( triggered() ), this, SLOT( addMssqlLayer() ) ); + connect( mActionAddOracleLayer, SIGNAL( triggered() ), this, SLOT( addOracleLayer() ) ); connect( mActionAddWmsLayer, SIGNAL( triggered() ), this, SLOT( addWmsLayer() ) ); connect( mActionAddWcsLayer, SIGNAL( triggered() ), this, SLOT( addWcsLayer() ) ); connect( mActionAddWfsLayer, SIGNAL( triggered() ), this, SLOT( addWfsLayer() ) ); @@ -1047,19 +1049,24 @@ void QgisApp::createActions() #ifndef HAVE_SPATIALITE delete mActionNewSpatialiteLayer; - mActionNewSpatialiteLayer = NULL; + mActionNewSpatialiteLayer = 0; delete mActionAddSpatiaLiteLayer; - mActionAddSpatiaLiteLayer = NULL; + mActionAddSpatiaLiteLayer = 0; #endif #ifndef HAVE_POSTGRESQL delete mActionAddPgLayer; - mActionAddPgLayer = NULL; + mActionAddPgLayer = 0; #endif #ifndef HAVE_MSSQL delete mActionAddMssqlLayer; - mActionAddMssqlLayer = NULL; + mActionAddMssqlLayer = 0; +#endif + +#ifndef HAVE_ORACLE + delete mActionAddOracleLayer; + mActionAddOracleLayer = 0; #endif } @@ -1645,6 +1652,9 @@ void QgisApp::setTheme( QString theThemeName ) #endif #ifdef HAVE_MSSQL mActionAddMssqlLayer->setIcon( QgsApplication::getThemeIcon( "/mActionAddMssqlLayer.png" ) ); +#endif +#ifdef HAVE_ORACLE + mActionAddOracleLayer->setIcon( QgsApplication::getThemeIcon( "/mActionAddOracleLayer.png" ) ); #endif mActionRemoveLayer->setIcon( QgsApplication::getThemeIcon( "/mActionRemoveLayer.png" ) ); mActionDuplicateLayer->setIcon( QgsApplication::getThemeIcon( "/mActionAddMap.png" ) ); @@ -2826,11 +2836,9 @@ void QgisApp::loadOGRSublayers( QString layertype, QString uri, QStringList list } } -#ifndef HAVE_POSTGRESQL -void QgisApp::addDatabaseLayer() {} -#else void QgisApp::addDatabaseLayer() { +#ifdef HAVE_POSTGRESQL if ( mMapCanvas && mMapCanvas->isDrawing() ) { return; @@ -2849,8 +2857,8 @@ void QgisApp::addDatabaseLayer() this , SLOT( addDatabaseLayers( QStringList const &, QString const & ) ) ); pgs->exec(); delete pgs; -} // QgisApp::addDatabaseLayer() #endif +} // QgisApp::addDatabaseLayer() void QgisApp::addDatabaseLayers( QStringList const & layerPathList, QString const & providerKey ) { @@ -2919,11 +2927,9 @@ void QgisApp::addDatabaseLayers( QStringList const & layerPathList, QString cons } -#ifndef HAVE_SPATIALITE -void QgisApp::addSpatiaLiteLayer() {} -#else void QgisApp::addSpatiaLiteLayer() { +#ifdef HAVE_SPATIALITE if ( mMapCanvas && mMapCanvas->isDrawing() ) { return; @@ -2940,15 +2946,12 @@ void QgisApp::addSpatiaLiteLayer() this , SLOT( addDatabaseLayers( QStringList const &, QString const & ) ) ); dbs->exec(); delete dbs; - -} // QgisApp::addSpatiaLiteLayer() #endif +} // QgisApp::addSpatiaLiteLayer() -#ifndef HAVE_MSSQL -void QgisApp::addMssqlLayer() {} -#else void QgisApp::addMssqlLayer() { +#ifdef HAVE_MSSQL if ( mMapCanvas && mMapCanvas->isDrawing() ) { return; @@ -2965,9 +2968,30 @@ void QgisApp::addMssqlLayer() this , SLOT( addDatabaseLayers( QStringList const &, QString const & ) ) ); dbs->exec(); delete dbs; - +#endif } // QgisApp::addMssqlLayer() + +void QgisApp::addOracleLayer() +{ +#ifdef HAVE_ORACLE + if ( mMapCanvas && mMapCanvas->isDrawing() ) + { + return; + } + + // show the Oracle dialog + QDialog *dbs = dynamic_cast( QgsProviderRegistry::instance()->selectWidget( QString( "oracle" ), this ) ); + if ( !dbs ) + { + QMessageBox::warning( this, tr( "Oracle" ), tr( "Cannot get Oracle select dialog from provider." ) ); + return; + } + connect( dbs , SIGNAL( addDatabaseLayers( QStringList const &, QString const & ) ), + this , SLOT( addDatabaseLayers( QStringList const &, QString const & ) ) ); + dbs->exec(); + delete dbs; #endif +} // QgisApp::addOracleLayer() void QgisApp::addWmsLayer() { @@ -7062,7 +7086,9 @@ void QgisApp::layersWereAdded( QList theLayers ) QgsVectorDataProvider* vProvider = vlayer->dataProvider(); if ( vProvider && vProvider->capabilities() & QgsVectorDataProvider::EditingCapabilities ) { +#if 0 connect( vlayer, SIGNAL( layerModified( bool ) ), this, SLOT( updateLayerModifiedActions() ) ); +#endif connect( vlayer, SIGNAL( editingStarted() ), this, SLOT( layerEditStateChanged() ) ); connect( vlayer, SIGNAL( editingStopped() ), this, SLOT( layerEditStateChanged() ) ); } diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 9f20b874de96..1d722fc34457 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -594,9 +594,13 @@ class QgisApp : public QMainWindow, private Ui::MainWindow void addSpatiaLiteLayer(); //#endif //#ifdef HAVE_MSSQL - //! Add a SpatiaLite layer to the map + //! Add a MSSQL layer to the map void addMssqlLayer(); //#endif + //#ifdef HAVE_ORACLE + //! Add a Oracle layer to the map + void addOracleLayer(); + //#endif /** toggles whether the current selected layer is in overview or not */ void isInOverview(); //! Slot to show the map coordinate position of the mouse cursor diff --git a/src/core/qgis.cpp b/src/core/qgis.cpp index 5f40481b42d5..150897d97a39 100644 --- a/src/core/qgis.cpp +++ b/src/core/qgis.cpp @@ -50,28 +50,6 @@ const QString GEOPROJ4 = "+proj=longlat +datum=WGS84 +no_defs"; const QString GEOPROJ4 = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"; #endif -const char* QGis::qgisVectorGeometryType[] = -{ - "Point", - "Line", - "Polygon", - "Unknown geometry", - "No geometry", -}; - -// description strings for feature types -const char* QGis::qgisFeatureTypes[] = -{ - "Null", - "WKBPoint", - "WKBLineString", - "WKBPolygon", - "WKBMultiPoint", - "WKBMultiLineString", - "WKBMultiPolygon" -}; - - const double QGis::DEFAULT_IDENTIFY_RADIUS = 0.5; // description strings for units diff --git a/src/core/qgis.h b/src/core/qgis.h index b362265e4401..9d49b52f47c6 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -67,24 +67,47 @@ class CORE_EXPORT QGis WKBMultiPolygon25D, }; + static WkbType singleType( WkbType type ) + { + switch ( type ) + { + case WKBMultiPoint: return WKBPoint; + case WKBMultiLineString: return WKBLineString; + case WKBMultiPolygon: return WKBPolygon; + case WKBMultiPoint25D: return WKBPoint25D; + case WKBMultiLineString25D: return WKBLineString25D; + case WKBMultiPolygon25D: return WKBPolygon25D; + default: return type; + } + } + static WkbType flatType( WkbType type ) { switch ( type ) { - case WKBMultiPoint: - return WKBPoint; - case WKBMultiLineString: - return WKBLineString; - case WKBMultiPolygon: - return WKBPolygon; - case WKBMultiPoint25D: - return WKBPoint25D; - case WKBMultiLineString25D: - return WKBLineString25D; - case WKBMultiPolygon25D: - return WKBPolygon25D; - default: - return type; + case WKBPoint25D: return WKBPoint; + case WKBLineString25D: return WKBLineString; + case WKBPolygon25D: return WKBPolygon; + case WKBMultiPoint25D: return WKBMultiPoint; + case WKBMultiLineString25D: return WKBMultiLineString; + case WKBMultiPolygon25D: return WKBMultiPolygon; + default: return type; + } + } + + static int wkbDimensions( WkbType type ) + { + switch ( type ) + { + case WKBUnknown: return 0; + case WKBNoGeometry: return 0; + case WKBPoint25D: return 3; + case WKBLineString25D: return 3; + case WKBPolygon25D: return 3; + case WKBMultiPoint25D: return 3; + case WKBMultiLineString25D: return 3; + case WKBMultiPolygon25D: return 3; + default: return 2; } } @@ -97,13 +120,41 @@ class CORE_EXPORT QGis NoGeometry }; - // String representation of geometry types (set in qgis.cpp) - //! @note not available in python bindings - static const char *qgisVectorGeometryType[]; + //! description strings for geometry types + static const char *vectorGeometryType( GeometryType type ) + { + switch ( type ) + { + case Point: return "Point"; + case Line: return "Line"; + case Polygon: return "Polygon"; + case UnknownGeometry: return "Unknown geometry"; + case NoGeometry: return "No geometry"; + default: return "Invalid type"; + } + } //! description strings for feature types - //! @note not available in python bindings - static const char *qgisFeatureTypes[]; + static const char *featureType( WkbType type ) + { + switch ( type ) + { + case WKBUnknown: return "WKBUnknown"; + case WKBPoint: return "WKBPoint"; + case WKBLineString: return "WKBLineString"; + case WKBPolygon: return "WKBPolygon"; + case WKBMultiPoint: return "WKBMultiLineString"; + case WKBMultiPolygon: return "WKBMultiPolygon"; + case WKBNoGeometry: return "WKBNoGeometry"; + case WKBPoint25D: return "WKBPoint25D"; + case WKBLineString25D: return "WKBLineString25D"; + case WKBPolygon25D: return "WKBPolygon25D"; + case WKBMultiPoint25D: return "WKBMultiPoint25D"; + case WKBMultiLineString25D: return "WKBMultiLineString25D"; + case WKBMultiPolygon25D: return "WKBMultiPolygon25D"; + default: return "invalid wkbtype"; + } + } /** Raster data types. * This is modified and extended copy of GDALDataType. diff --git a/src/core/qgsgeometry.cpp b/src/core/qgsgeometry.cpp index bf349874ec1b..3818b63313f3 100644 --- a/src/core/qgsgeometry.cpp +++ b/src/core/qgsgeometry.cpp @@ -1229,7 +1229,7 @@ GEOSGeometry* QgsGeometry::asGeos() QGis::WkbType QgsGeometry::wkbType() { unsigned char *geom = asWkb(); // ensure that wkb representation exists - if ( geom ) + if ( geom && wkbSize() >= 5 ) { unsigned int wkbType; memcpy( &wkbType, ( geom + 1 ), sizeof( wkbType ) ); @@ -1250,18 +1250,29 @@ QGis::GeometryType QgsGeometry::type() exportGeosToWkb(); } - QGis::WkbType type = wkbType(); - if ( type == QGis::WKBPoint || type == QGis::WKBPoint25D || - type == QGis::WKBMultiPoint || type == QGis::WKBMultiPoint25D ) - return QGis::Point; - if ( type == QGis::WKBLineString || type == QGis::WKBLineString25D || - type == QGis::WKBMultiLineString || type == QGis::WKBMultiLineString25D ) - return QGis::Line; - if ( type == QGis::WKBPolygon || type == QGis::WKBPolygon25D || - type == QGis::WKBMultiPolygon || type == QGis::WKBMultiPolygon25D ) - return QGis::Polygon; - - return QGis::UnknownGeometry; + switch ( wkbType() ) + { + case QGis::WKBPoint: + case QGis::WKBPoint25D: + case QGis::WKBMultiPoint: + case QGis::WKBMultiPoint25D: + return QGis::Point; + + case QGis::WKBLineString: + case QGis::WKBLineString25D: + case QGis::WKBMultiLineString: + case QGis::WKBMultiLineString25D: + return QGis::Line; + + case QGis::WKBPolygon: + case QGis::WKBPolygon25D: + case QGis::WKBMultiPolygon: + case QGis::WKBMultiPolygon25D: + return QGis::Polygon; + + default: + return QGis::UnknownGeometry; + } } bool QgsGeometry::isMultipart() @@ -4426,7 +4437,7 @@ QgsRectangle QgsGeometry::boundingBox() } default: - QgsDebugMsg( "Unknown WkbType ENCOUNTERED" ); + QgsDebugMsg( QString( "Unknown WkbType %1 ENCOUNTERED" ).arg( wkbType ) ); return QgsRectangle( 0, 0, 0, 0 ); break; diff --git a/src/core/qgsgeometry.h b/src/core/qgsgeometry.h index 01eb09897d62..edb3eb84e6b2 100644 --- a/src/core/qgsgeometry.h +++ b/src/core/qgsgeometry.h @@ -622,9 +622,6 @@ class CORE_EXPORT QgsGeometry /**Returns < 0 if point(x/y) is left of the line x1,y1 -> x1,y2*/ double leftOf( double x, double y, double& x1, double& y1, double& x2, double& y2 ); - - - static int refcount; }; // class QgsGeometry #endif diff --git a/src/core/qgsproviderregistry.cpp b/src/core/qgsproviderregistry.cpp index 1075f262f4da..42ed34ec9e11 100644 --- a/src/core/qgsproviderregistry.cpp +++ b/src/core/qgsproviderregistry.cpp @@ -526,10 +526,10 @@ const QgsProviderMetadata* QgsProviderRegistry::providerMetadata( const QString& } -/* +#if 0 QgsDataProvider * QgsProviderRegistry::openVector( QString const & dataSource, QString const & providerKey ) { - return getProvider( providerKey, dataSource ); + return getProvider( providerKey, dataSource ); } // QgsProviderRegistry::openVector -*/ +#endif diff --git a/src/core/qgsvectorfilewriter.cpp b/src/core/qgsvectorfilewriter.cpp index 2ec3bb5b5166..a50325cb595f 100644 --- a/src/core/qgsvectorfilewriter.cpp +++ b/src/core/qgsvectorfilewriter.cpp @@ -504,7 +504,7 @@ bool QgsVectorFileWriter::addFeature( QgsFeature& feature ) QgsGeometry *geom = feature.geometry(); // turn single geoemetry to multi geometry if needed - if ( geom && geom->wkbType() != mWkbType && geom->wkbType() == QGis::flatType( mWkbType ) ) + if ( geom && geom->wkbType() != mWkbType && geom->wkbType() == QGis::singleType( mWkbType ) ) { geom->convertToMultiType(); } diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index 10b8b6a5f2b6..51dd2cacc9fb 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -3015,7 +3015,7 @@ bool QgsVectorLayer::writeXml( QDomNode & layer_node, mapLayerNode.setAttribute( "type", "vector" ); // set the geometry type - mapLayerNode.setAttribute( "geometry", QGis::qgisVectorGeometryType[geometryType()] ); + mapLayerNode.setAttribute( "geometry", QGis::vectorGeometryType( geometryType() ) ); // add provider node if ( mDataProvider ) @@ -5799,7 +5799,7 @@ QString QgsVectorLayer::metadata() } else { - QString typeString( QGis::qgisVectorGeometryType[geometryType()] ); + QString typeString( QGis::vectorGeometryType( geometryType() ) ); myMetadata += "
LanguageFinished %Translators
German
99.9
Jürgen E. Fischer, Stephan Holl, Otto Dassau, Werner Macho
Galician (Spain)
97.8
Xan Vieiro
Spanish
95.6
Carlos Dávila, Javier César Aldariz, Gabriela Awad, Edwin Amado, Mayeul Kauffmann, Diana Galindo
Italian
90.3
Paolo Cavallini, Flavio Rigolon, Maurizio Napolitano, Roberto Angeletti, Alessandro Fanna, Michele Beneventi, Marco Braida, Luca Casagrande, Luca Delucchi, Anne Gishla
Swedish
89.2
Lars Luthman, Magnus Homann, Victor Axbom
Dutch
87.9
Richard Duivenvoorde, Raymond Nijssen, Carlo van Rijswijk
Japanese
86.5
BABA Yoshihiko, Yoichi Kayama
Estonian (Estonia)
86.4
Veiko Viil
French
86.3
Eve Rousseau, Marc Monnerat, Lionel Roubeyrie, Jean Roc Morreale, Benjamin Bohard, Jeremy Garniaux, Yves Jacolin, Benjamin Lerre, Stéphane Morel, Marie Silvestre, Tahir Tamba, Xavier M, Mayeul Kauffmann, Mehdi Semchaoui, Robin Cura, Etienne Tourigny, Mathieu Bossaert
Portuguese (Brazil)
86.3
Arthur Nanni
Polish (Poland)
86.2
Robert Szczepanek, Milena Nowotarska, Borys Jurgiel, Mateusz Loskot, Tomasz Paul, Andrzej Swiader
Russian
86.2
Artem Popov
Czech (Czech Republic)
85.6
Martin Landa, Peter Antolik, Martin Dzurov, Jan Helebrant
Hungarian
84.6
Zoltan Siki
Korean (Korea, Republic of)
84.0
BJ Jang
Slovenian (Slovenia)
80.2
Jože Detečnik, Dejan Gregor
Chinese (China)
75.4
Calvin Ngei, Zhang Jun
Latvian
70.5
Maris Nartiss, Pēteris Brūns
Serbian ()
68.7
Goran Ivanković
Serbian ()
68.7
Goran Ivanković
Portuguese (Portugal)
62.1
Giovanni Manghi, Joana Simoes, Duarte Carreira, Alexandre Neto, Pedro Pereira, Pedro Palheiro, Nelson Silva
Indonesian
59.6
Januar V. Simarmata, I Made Anombawa
Croatian (Croatia)
58.7
Zoran Jankovic
Thai
53.2
Man Chao
Ukrainian
49.2
Сергей Якунин
Turkish
47.1
Osman Yilmaz
Chinese (Taiwan, Province of China)
42.6
Nung-yao Lin
Vietnamese
40.6
Bùi Hữu Mạnh
Greek, Modern (1453-) (Greece)
38.8
Evripidis Argyropoulos, Mike Pegnigiannis, Nikos Ves
Icelandic
36.0
Thordur Ivarsson
Mongolian
34.4
Bayarmaa Enkhtur
Finnish
24.4
Marko Jarvenpaa
Danish (Denmark)
24.1
Preben Lisby
Georgian (Georgia)
23.0
Shota Murtskhvaladze, George Machitidze
Bulgarian
20.8
Захари Савов, Jordan Tzvetkov
Romanian
20.6
Lonut Losifescu-Enescu, Bogdan Pacurar
Slovak
19.7
Lubos Balazovic
Albanian (Albania)
17.4
Lao
15.7
Anousak Souphavanh, Soukanh Lathsavong
Persian
4.5
Mola Pahnadayan
Arabic
2.0
Assem Kamal, Latif Jalil
German
100.0
Jürgen E. Fischer, Stephan Holl, Otto Dassau, Werner Macho
Galician (Spain)
95.8
Xan Vieiro
Spanish
93.8
Carlos Dávila, Javier César Aldariz, Gabriela Awad, Edwin Amado, Mayeul Kauffmann, Diana Galindo
Italian
88.6
Paolo Cavallini, Flavio Rigolon, Maurizio Napolitano, Roberto Angeletti, Alessandro Fanna, Michele Beneventi, Marco Braida, Luca Casagrande, Luca Delucchi, Anne Gishla
Swedish
87.5
Lars Luthman, Magnus Homann, Victor Axbom
Dutch
86.3
Richard Duivenvoorde, Raymond Nijssen, Carlo van Rijswijk
Japanese
84.8
BABA Yoshihiko, Yoichi Kayama
Estonian (Estonia)
84.8
Veiko Viil
French
84.7
Eve Rousseau, Marc Monnerat, Lionel Roubeyrie, Jean Roc Morreale, Benjamin Bohard, Jeremy Garniaux, Yves Jacolin, Benjamin Lerre, Stéphane Morel, Marie Silvestre, Tahir Tamba, Xavier M, Mayeul Kauffmann, Mehdi Semchaoui, Robin Cura, Etienne Tourigny, Mathieu Bossaert
Portuguese (Brazil)
84.6
Arthur Nanni
Polish (Poland)
84.6
Robert Szczepanek, Milena Nowotarska, Borys Jurgiel, Mateusz Loskot, Tomasz Paul, Andrzej Swiader
Russian
84.5
Artem Popov
Czech (Czech Republic)
84.0
Martin Landa, Peter Antolik, Martin Dzurov, Jan Helebrant
Hungarian
83.0
Zoltan Siki
Korean (Korea, Republic of)
82.4
BJ Jang
Slovenian (Slovenia)
78.7
Jože Detečnik, Dejan Gregor
Chinese (China)
74.0
Calvin Ngei, Zhang Jun
Latvian
69.2
Maris Nartiss, Pēteris Brūns
Serbian ()
67.4
Goran Ivanković
Serbian ()
67.4
Goran Ivanković
Portuguese (Portugal)
60.9
Giovanni Manghi, Joana Simoes, Duarte Carreira, Alexandre Neto, Pedro Pereira, Pedro Palheiro, Nelson Silva
Indonesian
58.5
Januar V. Simarmata, I Made Anombawa
Croatian (Croatia)
57.6
Zoran Jankovic
Thai
52.2
Man Chao
Ukrainian
48.2
Сергей Якунин
Turkish
46.2
Osman Yilmaz
Chinese (Taiwan, Province of China)
41.8
Nung-yao Lin
Vietnamese
39.9
Bùi Hữu Mạnh
Greek, Modern (1453-) (Greece)
38.0
Evripidis Argyropoulos, Mike Pegnigiannis, Nikos Ves
Icelandic
35.3
Thordur Ivarsson
Mongolian
33.7
Bayarmaa Enkhtur
Finnish
24.0
Marko Jarvenpaa
Danish (Denmark)
23.7
Preben Lisby
Georgian (Georgia)
22.6
Shota Murtskhvaladze, George Machitidze
Bulgarian
20.4
Захари Савов, Jordan Tzvetkov
Romanian
20.2
Lonut Losifescu-Enescu, Bogdan Pacurar
Slovak
19.3
Lubos Balazovic
Albanian (Albania)
17.1
Lao
15.4
Anousak Souphavanh, Soukanh Lathsavong
Persian
4.4
Mola Pahnadayan
Arabic
1.9
Assem Kamal, Latif Jalil
Afrikaans
1.9
Hendrik Bosman
Lithuanian
0.7
Kestas M
Hebrew
0.1
"; myMetadata += tr( "Geometry type of the features in this layer: %1" ).arg( typeString ); diff --git a/src/core/qgsvectorlayerimport.cpp b/src/core/qgsvectorlayerimport.cpp index adb785e3f19f..2e2f88ae0eb9 100644 --- a/src/core/qgsvectorlayerimport.cpp +++ b/src/core/qgsvectorlayerimport.cpp @@ -24,6 +24,9 @@ #include "qgscoordinatereferencesystem.h" #include "qgsvectorlayerimport.h" #include "qgsproviderregistry.h" +#include "qgsdatasourceuri.h" + +#include #include @@ -176,6 +179,17 @@ bool QgsVectorLayerImport::flushBuffer() return true; } +bool QgsVectorLayerImport::createSpatialIndex() +{ + if ( mProvider && ( mProvider->capabilities() & QgsVectorDataProvider::CreateSpatialIndex ) != 0 ) + { + return mProvider->createSpatialIndex(); + } + else + { + return true; + } +} QgsVectorLayerImport::ImportError QgsVectorLayerImport::importLayer( QgsVectorLayer* layer, @@ -384,6 +398,14 @@ QgsVectorLayerImport::importLayer( QgsVectorLayer* layer, } int errors = writer->errorCount(); + if ( !writer->createSpatialIndex() ) + { + if ( writer->hasError() && errorMessage ) + { + *errorMessage += "\n" + writer->errorMessage(); + } + } + delete writer; if ( shallTransform ) diff --git a/src/core/qgsvectorlayerimport.h b/src/core/qgsvectorlayerimport.h index 3f9cee9102ee..efc03d35983c 100644 --- a/src/core/qgsvectorlayerimport.h +++ b/src/core/qgsvectorlayerimport.h @@ -94,6 +94,9 @@ class CORE_EXPORT QgsVectorLayerImport /** flush the buffer writing the features to the new layer */ bool flushBuffer(); + /** create index */ + bool createSpatialIndex(); + /** contains error value */ ImportError mError; QString mErrorMessage; diff --git a/src/core/symbology-ng/qgsrendererv2.cpp b/src/core/symbology-ng/qgsrendererv2.cpp index 351e84254adb..dc00d811f2a4 100644 --- a/src/core/symbology-ng/qgsrendererv2.cpp +++ b/src/core/symbology-ng/qgsrendererv2.cpp @@ -348,7 +348,7 @@ void QgsFeatureRendererV2::renderFeatureWithSymbol( QgsFeature& feature, QgsSymb break; default: - QgsDebugMsg( "unsupported wkb type for rendering" ); + QgsDebugMsg( QString( "unsupported wkb type 0x%1 for rendering" ).arg( geom->wkbType(), 0, 16 ) ); } } diff --git a/src/gui/attributetable/qgsattributetablemodel.cpp b/src/gui/attributetable/qgsattributetablemodel.cpp index 41bea00676fe..c2d68ba6258b 100644 --- a/src/gui/attributetable/qgsattributetablemodel.cpp +++ b/src/gui/attributetable/qgsattributetablemodel.cpp @@ -138,7 +138,7 @@ bool QgsAttributeTableModel::removeRows( int row, int count, const QModelIndex & void QgsAttributeTableModel::featureAdded( QgsFeatureId fid, bool newOperation ) { - QgsDebugMsgLevel( QString( "feature %1 added (%2, rows %3, ids %4)" ).arg( fid ).arg( newOperation ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 3 ); + QgsDebugMsgLevel( QString( "feature %1 added (%2, rows %3, ids %4)" ).arg( fid ).arg( newOperation ).arg( mRowIdMap.size() ).arg( mIdRowMap.size() ), 4 ); int n = mRowIdMap.size(); if ( newOperation ) diff --git a/src/gui/qgsfieldvalidator.cpp b/src/gui/qgsfieldvalidator.cpp index 8cafdd5e1bdd..73d9c814dcde 100644 --- a/src/gui/qgsfieldvalidator.cpp +++ b/src/gui/qgsfieldvalidator.cpp @@ -132,7 +132,7 @@ QValidator::State QgsFieldValidator::validate( QString &s, int &i ) const } else { - QgsDebugMsg( "unsupported type for validation" ); + QgsDebugMsg( QString( "unsupported type %1 for validation" ).arg( mField.type() ) ); return Invalid; } diff --git a/src/gui/qgsmanageconnectionsdialog.cpp b/src/gui/qgsmanageconnectionsdialog.cpp index 6dcd313ac798..faf45e0bb23d 100644 --- a/src/gui/qgsmanageconnectionsdialog.cpp +++ b/src/gui/qgsmanageconnectionsdialog.cpp @@ -114,6 +114,9 @@ void QgsManageConnectionsDialog::doExportImport() case WCS: doc = saveOWSConnections( items, "WCS" ); break; + case Oracle: + doc = saveOracleConnections( items ); + break; } QFile file( mFileName ); @@ -173,6 +176,9 @@ void QgsManageConnectionsDialog::doExportImport() case WCS: loadOWSConnections( doc, items, "WCS" ); break; + case Oracle: + loadOracleConnections( doc, items ); + break; } // clear connections list and close window listConnections->clear(); @@ -205,6 +211,9 @@ bool QgsManageConnectionsDialog::populateConnections() case MSSQL: settings.beginGroup( "/MSSQL/connections" ); break; + case Oracle: + settings.beginGroup( "/Oracle/connections" ); + break; } QStringList keys = settings.childGroups(); QStringList::Iterator it = keys.begin(); @@ -292,6 +301,14 @@ bool QgsManageConnectionsDialog::populateConnections() return false; } break; + case Oracle: + if ( root.tagName() != "qgsOracleConnections" ) + { + QMessageBox::information( this, tr( "Loading connections" ), + tr( "The file is not an Oracle connections exchange file." ) ); + return false; + } + break; } QDomElement child = root.firstChildElement(); @@ -446,6 +463,46 @@ QDomDocument QgsManageConnectionsDialog::saveMssqlConnections( const QStringList return doc; } +QDomDocument QgsManageConnectionsDialog::saveOracleConnections( const QStringList &connections ) +{ + QDomDocument doc( "connections" ); + QDomElement root = doc.createElement( "qgsOracleConnections" ); + root.setAttribute( "version", "1.0" ); + doc.appendChild( root ); + + QSettings settings; + QString path; + for ( int i = 0; i < connections.count(); ++i ) + { + path = "/Oracle/connections/" + connections[ i ]; + QDomElement el = doc.createElement( "oracle" ); + el.setAttribute( "name", connections[ i ] ); + el.setAttribute( "host", settings.value( path + "/host", "" ).toString() ); + el.setAttribute( "port", settings.value( path + "/port", "" ).toString() ); + el.setAttribute( "database", settings.value( path + "/database", "" ).toString() ); + el.setAttribute( "sslmode", settings.value( path + "/sslmode", "1" ).toString() ); + el.setAttribute( "estimatedMetadata", settings.value( path + "/estimatedMetadata", "0" ).toString() ); + + el.setAttribute( "saveUsername", settings.value( path + "/saveUsername", "false" ).toString() ); + + if ( settings.value( path + "/saveUsername", "false" ).toString() == "true" ) + { + el.setAttribute( "username", settings.value( path + "/username", "" ).toString() ); + } + + el.setAttribute( "savePassword", settings.value( path + "/savePassword", "false" ).toString() ); + + if ( settings.value( path + "/savePassword", "false" ).toString() == "true" ) + { + el.setAttribute( "password", settings.value( path + "/password", "" ).toString() ); + } + + root.appendChild( el ); + } + + return doc; +} + void QgsManageConnectionsDialog::loadOWSConnections( const QDomDocument &doc, const QStringList &items, const QString &service ) { QDomElement root = doc.documentElement(); @@ -708,7 +765,7 @@ void QgsManageConnectionsDialog::loadMssqlConnections( const QDomDocument &doc, { QMessageBox::information( this, tr( "Loading connections" ), - tr( "The file is not an PostGIS connections exchange file." ) ); + tr( "The file is not an MSSQL connections exchange file." ) ); return; } @@ -791,6 +848,88 @@ void QgsManageConnectionsDialog::loadMssqlConnections( const QDomDocument &doc, } } +void QgsManageConnectionsDialog::loadOracleConnections( const QDomDocument &doc, const QStringList &items ) +{ + QDomElement root = doc.documentElement(); + if ( root.tagName() != "qgsOracleConnections" ) + { + QMessageBox::information( this, + tr( "Loading connections" ), + tr( "The file is not an Oracle connections exchange file." ) ); + return; + } + + QString connectionName; + QSettings settings; + settings.beginGroup( "/Oracle/connections" ); + QStringList keys = settings.childGroups(); + settings.endGroup(); + QDomElement child = root.firstChildElement(); + bool prompt = true; + bool overwrite = true; + + while ( !child.isNull() ) + { + connectionName = child.attribute( "name" ); + if ( !items.contains( connectionName ) ) + { + child = child.nextSiblingElement(); + continue; + } + + // check for duplicates + if ( keys.contains( connectionName ) && prompt ) + { + int res = QMessageBox::warning( this, + tr( "Loading connections" ), + tr( "Connection with name '%1' already exists. Overwrite?" ) + .arg( connectionName ), + QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel ); + switch ( res ) + { + case QMessageBox::Cancel: + return; + case QMessageBox::No: + child = child.nextSiblingElement(); + continue; + case QMessageBox::Yes: + overwrite = true; + break; + case QMessageBox::YesToAll: + prompt = false; + overwrite = true; + break; + case QMessageBox::NoToAll: + prompt = false; + overwrite = false; + break; + } + } + + if ( keys.contains( connectionName ) && !overwrite ) + { + child = child.nextSiblingElement(); + continue; + } + + //no dups detected or overwrite is allowed + settings.beginGroup( "/Oracle/connections/" + connectionName ); + + settings.setValue( "/host", child.attribute( "host" ) ); + settings.setValue( "/port", child.attribute( "port" ) ); + settings.setValue( "/database", child.attribute( "database" ) ); + settings.setValue( "/sslmode", child.attribute( "sslmode" ) ); + settings.setValue( "/estimatedMetadata", child.attribute( "estimatedMetadata" ) ); + settings.setValue( "/saveUsername", child.attribute( "saveUsername" ) ); + settings.setValue( "/username", child.attribute( "username" ) ); + settings.setValue( "/savePassword", child.attribute( "savePassword" ) ); + settings.setValue( "/password", child.attribute( "password" ) ); + settings.endGroup(); + + child = child.nextSiblingElement(); + } +} + void QgsManageConnectionsDialog::selectAll() { listConnections->selectAll(); diff --git a/src/gui/qgsmanageconnectionsdialog.h b/src/gui/qgsmanageconnectionsdialog.h index 1b67e507077b..566406f18f50 100644 --- a/src/gui/qgsmanageconnectionsdialog.h +++ b/src/gui/qgsmanageconnectionsdialog.h @@ -39,7 +39,8 @@ class GUI_EXPORT QgsManageConnectionsDialog : public QDialog, private Ui::QgsMan PostGIS, WFS, MSSQL, - WCS + WCS, + Oracle, }; // constructor @@ -54,14 +55,18 @@ class GUI_EXPORT QgsManageConnectionsDialog : public QDialog, private Ui::QgsMan private: bool populateConnections(); + QDomDocument saveOWSConnections( const QStringList &connections, const QString &service ); QDomDocument saveWFSConnections( const QStringList &connections ); QDomDocument savePgConnections( const QStringList & connections ); QDomDocument saveMssqlConnections( const QStringList & connections ); + QDomDocument saveOracleConnections( const QStringList & connections ); + void loadOWSConnections( const QDomDocument &doc, const QStringList &items, const QString &service ); void loadWFSConnections( const QDomDocument &doc, const QStringList &items ); void loadPgConnections( const QDomDocument &doc, const QStringList &items ); void loadMssqlConnections( const QDomDocument &doc, const QStringList &items ); + void loadOracleConnections( const QDomDocument &doc, const QStringList &items ); QString mFileName; Mode mDialogMode; diff --git a/src/plugins/grass/qgsgrassmodule.cpp b/src/plugins/grass/qgsgrassmodule.cpp index 2e6e4a24f54c..cd848c71224d 100644 --- a/src/plugins/grass/qgsgrassmodule.cpp +++ b/src/plugins/grass/qgsgrassmodule.cpp @@ -3621,7 +3621,7 @@ void QgsGrassModuleField::updateFields() std::vector fields = mLayerInput->currentFields(); - for ( unsigned int i = 0; i < fields.size(); i++ ) + for ( int i = 0; i < fields.size(); i++ ) { if ( mType.contains( fields[i].typeName() ) ) { diff --git a/src/providers/CMakeLists.txt b/src/providers/CMakeLists.txt index 8e8b9bc65314..e5588c68eddc 100644 --- a/src/providers/CMakeLists.txt +++ b/src/providers/CMakeLists.txt @@ -15,6 +15,10 @@ ADD_SUBDIRECTORY(wcs) ADD_SUBDIRECTORY(gpx) ADD_SUBDIRECTORY(wfs) +IF (WITH_ORACLE) + ADD_SUBDIRECTORY(oracle) +ENDIF(WITH_ORACLE) + IF (POSTGRES_FOUND) ADD_SUBDIRECTORY(postgres) ENDIF (POSTGRES_FOUND) diff --git a/src/providers/mssql/qgsmssqlsourceselect.h b/src/providers/mssql/qgsmssqlsourceselect.h index efc79331c00b..4e50ac35bdb7 100644 --- a/src/providers/mssql/qgsmssqlsourceselect.h +++ b/src/providers/mssql/qgsmssqlsourceselect.h @@ -33,7 +33,6 @@ class QPushButton; class QStringList; class QgsGeomColumnTypeThread; class QgisApp; -class QgsPgSourceSelect; class QgsMssqlSourceSelectDelegate : public QItemDelegate { diff --git a/src/providers/oracle/CMakeLists.txt b/src/providers/oracle/CMakeLists.txt new file mode 100644 index 000000000000..6cfb2f0a7ae0 --- /dev/null +++ b/src/providers/oracle/CMakeLists.txt @@ -0,0 +1,54 @@ +######################################################## +# Files + +ADD_SUBDIRECTORY(ocispatial) + +SET(ORACLE_SRCS + qgsoracleprovider.cpp + qgsoracleconn.cpp + qgsoracledataitems.cpp + qgsoraclesourceselect.cpp + qgsoraclenewconnection.cpp + qgsoracletablemodel.cpp + qgsoraclecolumntypethread.cpp +) + +SET(ORACLE_MOC_HDRS + qgsoracleprovider.h + qgsoracleconn.h + qgsoracledataitems.h + qgsoraclesourceselect.h + qgsoraclenewconnection.h + qgsoracletablemodel.h + qgsoraclecolumntypethread.h +) + + +######################################################## +# Build + +QT4_WRAP_CPP(ORACLE_MOC_SRCS ${ORACLE_MOC_HDRS}) + +INCLUDE_DIRECTORIES( + ../../core + ../../gui + ${GEOS_INCLUDE_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/../../ui + ${QT_QTSQL_INCLUDEDIR} +) + +ADD_LIBRARY (oracleprovider MODULE ${ORACLE_SRCS} ${ORACLE_MOC_SRCS}) + +TARGET_LINK_LIBRARIES (oracleprovider + qgis_core + qgis_gui + ${QT_QTSQL_LIBRARY} +) + + +######################################################## +# Install + +INSTALL(TARGETS oracleprovider + RUNTIME DESTINATION ${QGIS_PLUGIN_DIR} + LIBRARY DESTINATION ${QGIS_PLUGIN_DIR}) diff --git a/src/providers/oracle/ocispatial/CMakeLists.txt b/src/providers/oracle/ocispatial/CMakeLists.txt new file mode 100644 index 000000000000..5f54056ad70e --- /dev/null +++ b/src/providers/oracle/ocispatial/CMakeLists.txt @@ -0,0 +1,32 @@ +SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH}) +SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${QGIS_OUTPUT_DIRECTORY}/${QGIS_PLUGIN_SUBDIR}/sqldrivers) +SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${QGIS_OUTPUT_DIRECTORY}/${QGIS_PLUGIN_SUBDIR}/sqldrivers) + +FIND_PACKAGE(OCI) + +ADD_DEFINITIONS(${QT_DEFINITIONS}) +ADD_DEFINITIONS(-DQT_PLUGIN) +ADD_DEFINITIONS(-DQT_NO_DEBUG) +ADD_DEFINITIONS(-DQT_SHARED) + +INCLUDE_DIRECTORIES(${OCI_INCLUDE_DIR}) + +SET(QSQLOCISPATIAL_SRC qsql_ocispatial.cpp main.cpp) +QT4_WRAP_CPP(QSQLOCISPATIAL_SRC qsql_ocispatial.h) + +ADD_LIBRARY(qsqlocispatial SHARED ${QSQLOCISPATIAL_SRC}) + +TARGET_LINK_LIBRARIES(qsqlocispatial + ${QT_QTCORE_LIBRARY} + ${QT_QTSQL_LIBRARY} + ${OCI_LIBRARY} +) + +IF(MSVC) + TARGET_LINK_LIBRARIES(qsqlocispatial wsock32) +ENDIF(MSVC) + +INSTALL(TARGETS qsqlocispatial + RUNTIME DESTINATION ${QT_PLUGINS_DIR}/sqldrivers + LIBRARY DESTINATION ${QT_PLUGINS_DIR}/sqldrivers +) diff --git a/src/providers/oracle/ocispatial/README b/src/providers/oracle/ocispatial/README new file mode 100644 index 000000000000..226fefe0d8f8 --- /dev/null +++ b/src/providers/oracle/ocispatial/README @@ -0,0 +1,7 @@ +QOCISPATIAL driver derived from QOCI driver. + +You will need the Oracle development headers and libraries installed +before compiling this plugin. + +See the Qt SQL documentation for more information on compiling Qt SQL +driver plugins (sql-driver.html). diff --git a/src/providers/oracle/ocispatial/cmake/FindOCI.cmake b/src/providers/oracle/ocispatial/cmake/FindOCI.cmake new file mode 100644 index 000000000000..deb0153df6c2 --- /dev/null +++ b/src/providers/oracle/ocispatial/cmake/FindOCI.cmake @@ -0,0 +1,45 @@ +# ~~~~~~~~~~ +# Copyright (c) 2012, Juergen E. Fischer +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +# CMake module to search for OCI library +# +# If it's found it sets OCI_FOUND to TRUE +# and following variables are set: +# OCI_INCLUDE_DIR +# OCI_LIBRARY + +FIND_PATH(OCI_INCLUDE_DIR oci.h + PATHS + /usr/include/oracle/11.2/client64 + $ENV{OSGEO4W_ROOT}/include + $ENV{ORACLE_HOME}/rdbms/public +) + +FIND_LIBRARY(OCI_LIBRARY clntsh oci + PATHS + /usr/lib/oracle/11.2/client64/lib/ + $ENV{OSGEO4W_ROOT}/lib + $ENV{ORACLE_HOME}/lib +) + +IF (OCI_INCLUDE_DIR) + SET(OCI_FOUND TRUE) +ELSE (OCI_INCLUDE_DIR) + SET(OCI_FOUND FALSE) +ENDIF(OCI_INCLUDE_DIR) + +IF (OCI_FOUND) + IF (NOT OCI_FIND_QUIETLY) + MESSAGE(STATUS "Found OCI: ${OCI_LIBRARY}") + ENDIF (NOT OCI_FIND_QUIETLY) +ELSE (OCI_FOUND) + IF (OCI_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find OCI") + ELSE (OCI_FIND_REQUIRED) + IF (NOT OCI_FIND_QUIETLY) + MESSAGE(STATUS "Could not find OCI") + ENDIF (NOT OCI_FIND_QUIETLY) + ENDIF (OCI_FIND_REQUIRED) +ENDIF (OCI_FOUND) diff --git a/src/providers/oracle/ocispatial/main.cpp b/src/providers/oracle/ocispatial/main.cpp new file mode 100644 index 000000000000..d4c97dae0659 --- /dev/null +++ b/src/providers/oracle/ocispatial/main.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +** Oracle Spatial Support: (C) 2012-2013 Juergen E. Fischer < jef at norbit dot de >, norBIT GmbH +** +****************************************************************************/ + +#include +#include +#include "qsql_ocispatial.h" + +QT_BEGIN_NAMESPACE + +class QOCISpatialDriverPlugin : public QSqlDriverPlugin +{ + public: + QOCISpatialDriverPlugin(); + + QSqlDriver* create( const QString & ); + QStringList keys() const; +}; + +QOCISpatialDriverPlugin::QOCISpatialDriverPlugin() + : QSqlDriverPlugin() +{ +} + +QSqlDriver* QOCISpatialDriverPlugin::create( const QString &name ) +{ + if ( name == QLatin1String( "QOCISPATIAL" ) || name == QLatin1String( "QOCISPATIAL8" ) ) + { + QOCISpatialDriver* driver = new QOCISpatialDriver(); + return driver; + } + return 0; +} + +QStringList QOCISpatialDriverPlugin::keys() const +{ + QStringList l; + l.append( QLatin1String( "QOCISPATIAL8" ) ); + l.append( QLatin1String( "QOCISPATIAL" ) ); + return l; +} + +Q_EXPORT_STATIC_PLUGIN( QOCISpatialDriverPlugin ) +Q_EXPORT_PLUGIN2( qsqloci, QOCISpatialDriverPlugin ) + +QT_END_NAMESPACE diff --git a/src/providers/oracle/ocispatial/qsql_ocispatial.cpp b/src/providers/oracle/ocispatial/qsql_ocispatial.cpp new file mode 100644 index 000000000000..32a0cae0d1cc --- /dev/null +++ b/src/providers/oracle/ocispatial/qsql_ocispatial.cpp @@ -0,0 +1,3983 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +** Oracle Spatial support: (C) 2012-2013 Juergen E. Fischer < jef at norbit dot de >, norBIT GmbH +** +****************************************************************************/ +// #define QOCISPATIAL_DEBUG +#ifndef QOCISPATIAL_DEBUG +#define QT_NO_DEBUG_OUTPUT +#endif + +#include "qsql_ocispatial.h" +#include "wkbptr.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#else +#include +#endif + +// This is needed for oracle oci when compiling with mingw-w64 headers +#if defined(__MINGW64_VERSION_MAJOR) && defined(_WIN64) +#define _int64 __int64 +#endif + + +#include +#ifdef max +#undef max +#endif +#ifdef min +#undef min +#endif + +#include + +#define QOCISPATIAL_DYNAMIC_CHUNK_SIZE 65535 +#define QOCISPATIAL_PREFETCH_MEM 10240 + +// setting this define will allow using a query from a different +// thread than its database connection. +// warning - this is not fully tested and can lead to race conditions +#define QOCISPATIAL_THREADED + + +Q_DECLARE_METATYPE( OCIEnv* ) +Q_DECLARE_METATYPE( OCIStmt* ) + +QT_BEGIN_NAMESPACE + +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN +enum { QOCISpatialEncoding = 2002 }; // AL16UTF16LE +#else +enum { QOCISpatialEncoding = 2000 }; // AL16UTF16 +#endif + +#ifdef OCI_ATTR_CHARSET_FORM +// Always set the OCI_ATTR_CHARSET_FORM to SQLCS_NCHAR is safe +// because Oracle server will deal with the implicit Conversion +// Between CHAR and NCHAR. +// see: http://download.oracle.com/docs/cd/A91202_01/901_doc/appdev.901/a89857/oci05bnd.htm#422705 +static const ub1 qOraCharsetForm = SQLCS_NCHAR; +#endif + +#if defined (OCI_UTF16ID) +static const ub2 qOraCharset = OCI_UTF16ID; +#else +static const ub2 qOraCharset = OCI_UCS2ID; +#endif + +typedef QVarLengthArray IndicatorArray; +typedef QVarLengthArray SizeArray; + +static QByteArray qMakeOraDate( const QDateTime& dt ); +static QDateTime qMakeDate( const char* oraDate ); + +static QByteArray qMakeOCINumber( const qlonglong &ll, OCIError *err ); +static QByteArray qMakeOCINumber( const qulonglong& ull, OCIError* err ); + +static qlonglong qMakeLongLong( const char* ociNumber, OCIError* err ); +static qulonglong qMakeULongLong( const char* ociNumber, OCIError* err ); + +static QString qOraWarn( OCIError *err, int *errorCode = 0 ); + +#ifndef Q_CC_SUN +static // for some reason, Sun CC can't use qOraWarning when it's declared static +#endif +void qOraWarningAt( const char* msg, OCIError *err, const char *function, int line ); +static QSqlError qMakeError( const QString& errString, QSqlError::ErrorType type, OCIError *err ); + +#ifndef _MSC_VER +#define qOraWarning(msg,err) qOraWarningAt(msg,err,__PRETTY_FUNCTION__,__LINE__) +#define OCI_VERIFY(x) do { oci_verify(__PRETTY_FUNCTION__, __FILE__, __LINE__, x, #x); } while(0) +#else +#define qOraWarning(msg,err) qOraWarningAt(msg,err,__FUNCTION__,__LINE__) +#define OCI_VERIFY(x) do { oci_verify(__FUNCTION__, __FILE__, __LINE__, x, #x); } while(0) +#endif + +void oci_verify( const char *function, const char *file, int line, int result, const char *expression ) +{ + if ( result == OCI_SUCCESS || result == OCI_SUCCESS_WITH_INFO ) + return; + + qWarning( "%s:%d (%s) OCI error %s = %d", file, line, function, expression, result ); + throw result; +} + +#ifdef QOCISPATIAL_DEBUG +class enter +{ + const char *mFunction; + const char *mFile; + int mLine; + static int level; + + public: + enter( const char *function, const char *file, int line ) + : mFunction( function ), mFile( file ), mLine( line ) + { + qDebug( "+%*sEntering %s at %s:%d", level, "", mFunction, mFile, mLine ); + level++; + } + + ~enter() + { + level--; + qDebug( "-%*sLeaving %s", level, "", mFunction ); + } +}; + +#define ENTER enter here(__PRETTY_FUNCTION__,__FILE__,__LINE__); + +int enter::level = 0; +#else +#define ENTER +#endif + +enum WKBType +{ + WKBUnknown = 0, + WKBPoint = 1, + WKBLineString, + WKBPolygon, + WKBMultiPoint, + WKBMultiLineString, + WKBMultiPolygon, + WKBNoGeometry = 100, //attributes only + WKBPoint25D = 0x80000001, + WKBLineString25D, + WKBPolygon25D, + WKBMultiPoint25D, + WKBMultiLineString25D, + WKBMultiPolygon25D, + +#if 0 + WKBGeometry = 0, + WKBPoint = 1, + WKBLineString = 2, + WKBPolygon = 3, + WKBMultiPoint = 4, + WKBMultiLineString = 5, + WKBMultiPolygon = 6, + WKBGeometryCollection = 7, + WKBPolyhedralSurface = 15, + WKBTIN = 16, + WKBTriangle = 17, + + WKBGeometryZ = 1000, + WKBPointZ = 1001, + WKBLineStringZ = 1002, + WKBPolygonZ = 1003, + WKBMultiPointZ = 1004, + WKBMultiLineStringZ = 1005, + WKBMultiPolygonZ = 1006, + WKBGeometryCollectionZ = 1007, + WKBPolyhedralSurfaceZ = 1015, + WKBTINZ = 1016, + WKBTriangleZ = 1017, + + WKBGeometryM = 2000, + WKBPointM = 2001, + WKBLineStringM = 2002, + WKBPolygonM = 2003, + WKBMultiPointM = 2004, + WKBMultiLineStringM = 2005, + WKBMultiPolygonM = 2006, + WKBGeometryCollectionM = 2007, + WKBPolyhedralSurfaceM = 2015, + WKBTINM = 2016, + WKBTriangleM = 2017, + + WKBGeometryZM = 3000, + WKBPointZM = 3001, + WKBLineStringZM = 3002, + WKBPolygonZM = 3003, + WKBMultiPointZM = 3004, + WKBMultiLineStringZM = 3005, + WKBMultiPolygonZM = 3006, + WKBGeometryCollectionZM = 3007, + WKBPolyhedralSurfaceZM = 3015, + WKBTINZM = 3016, + WKBTriangleZM = 3017, +#endif +}; + + +class QOCISpatialRowId: public QSharedData +{ + public: + QOCISpatialRowId( OCIEnv *env ); + ~QOCISpatialRowId(); + + OCIRowid *id; + + private: + QOCISpatialRowId( const QOCISpatialRowId &other ): QSharedData( other ) { Q_ASSERT( false ); } +}; + +QOCISpatialRowId::QOCISpatialRowId( OCIEnv *env ) + : id( 0 ) +{ + OCIDescriptorAlloc( env, reinterpret_cast( &id ), + OCI_DTYPE_ROWID, 0, 0 ); +} + +QOCISpatialRowId::~QOCISpatialRowId() +{ + if ( id ) + OCIDescriptorFree( id, OCI_DTYPE_ROWID ); +} + +typedef QSharedDataPointer QOCISpatialRowIdPointer; +QT_BEGIN_INCLUDE_NAMESPACE +Q_DECLARE_METATYPE( QOCISpatialRowIdPointer ) +QT_END_INCLUDE_NAMESPACE + +class QOCISpatialCols; + +struct QOCISDOPointObj +{ + OCINumber x; + OCINumber y; + OCINumber z; +}; + +struct QOCISDOGeometryObj +{ + OCINumber gtype; + OCINumber srid; + QOCISDOPointObj point; + OCIArray *elem_info; + OCIArray *ordinates; +}; + +struct QOCISDOPointInd +{ + OCIInd _atomic; + OCIInd x; + OCIInd y; + OCIInd z; +}; + +struct QOCISDOGeometryInd +{ + OCIInd _atomic; + OCIInd gtype; + OCIInd srid; + QOCISDOPointInd point; + OCIInd elem_info; + OCIInd ordinates; +}; + +struct QOCISpatialResultPrivate +{ + QOCISpatialResultPrivate( QOCISpatialResult *result, const QOCISpatialDriverPrivate *driver ); + ~QOCISpatialResultPrivate(); + + QOCISpatialCols *cols; + QOCISpatialResult *q; + OCIEnv *env; + OCIError *err; + OCISvcCtx *&svc; + OCIStmt *sql; + QOCISDOGeometryObj *sdoobj; + QOCISDOGeometryInd *sdoind; + bool transaction; + int serverVersion; + int prefetchRows, prefetchMem; + OCIType *geometryTDO; + QOCISDOGeometryObj *geometryObj; + QOCISDOGeometryInd *geometryInd; + + void setStatementAttributes(); + int bindValue( OCIStmt *sql, OCIBind **hbnd, OCIError *err, int pos, + const QVariant &val, dvoid *indPtr, ub2 *tmpSize, QList &tmpStorage ); + int bindValues( QVector &values, IndicatorArray &indicators, SizeArray &tmpSizes, + QList &tmpStorage ); + void outValues( QVector &values, IndicatorArray &indicators, + QList &tmpStorage ); + inline bool isOutValue( int i ) const + { return q->bindValueType( i ) & QSql::Out; } + inline bool isBinaryValue( int i ) const + { return q->bindValueType( i ) & QSql::Binary; } + + void setCharset( dvoid* handle, ub4 type ) const + { + int r = OCI_SUCCESS; + Q_ASSERT( handle ); + +#ifdef OCI_ATTR_CHARSET_FORM + r = OCIAttrSet( handle, + type, + // this const cast is safe since OCI doesn't touch + // the charset. + const_cast( static_cast( &qOraCharsetForm ) ), + 0, + OCI_ATTR_CHARSET_FORM, + //Strange Oracle bug: some Oracle servers crash the server process with non-zero error handle (mostly for 10g). + //So ignore the error message here. + 0 ); +#ifdef QOCISPATIAL_DEBUG + if ( r != OCI_SUCCESS ) + qWarning( "QOCISpatialResultPrivate::setCharset: Couldn't set OCI_ATTR_CHARSET_FORM." ); +#endif +#endif + + r = OCIAttrSet( handle, + type, + // this const cast is safe since OCI doesn't touch + // the charset. + const_cast( static_cast( &qOraCharset ) ), + 0, + OCI_ATTR_CHARSET_ID, + err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "Couldn't set OCI_ATTR_CHARSET_ID: ", err ); + + } +}; + +void QOCISpatialResultPrivate::setStatementAttributes() +{ + ENTER + Q_ASSERT( sql ); + + int r = OCI_SUCCESS; + + if ( prefetchRows >= 0 ) + { + r = OCIAttrSet( sql, + OCI_HTYPE_STMT, + &prefetchRows, + 0, + OCI_ATTR_PREFETCH_ROWS, + err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "Couldn't set OCI_ATTR_PREFETCH_ROWS: ", err ); + } + if ( prefetchMem >= 0 ) + { + r = OCIAttrSet( sql, + OCI_HTYPE_STMT, + &prefetchMem, + 0, + OCI_ATTR_PREFETCH_MEMORY, + err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "Couldn't set OCI_ATTR_PREFETCH_MEMORY: ", err ); + } +} + +int QOCISpatialResultPrivate::bindValue( OCIStmt *sql, OCIBind **hbnd, OCIError *err, int pos, + const QVariant &val, dvoid *indPtr, ub2 *tmpSize, QList &tmpStorage ) +{ + ENTER + int r = OCI_SUCCESS; + void *data = const_cast( val.constData() ); + + switch ( val.type() ) + { + case QVariant::ByteArray: + r = OCIBindByPos( sql, hbnd, err, + pos + 1, + isOutValue( pos ) + ? const_cast( reinterpret_cast( data )->constData() ) + : reinterpret_cast( data )->data(), + reinterpret_cast( data )->size(), + SQLT_BIN, indPtr, 0, 0, 0, 0, OCI_DEFAULT ); + qDebug() << "inout" << isOutValue( pos ) << "bytearray size" << reinterpret_cast( data )->size() << "r" << r; + break; + case QVariant::Time: + case QVariant::Date: + case QVariant::DateTime: + { + QByteArray ba = qMakeOraDate( val.toDateTime() ); + r = OCIBindByPos( sql, hbnd, err, + pos + 1, + ba.data(), + ba.size(), + SQLT_DAT, indPtr, 0, 0, 0, 0, OCI_DEFAULT ); + tmpStorage.append( ba ); + break; + } + case QVariant::Int: + r = OCIBindByPos( sql, hbnd, err, + pos + 1, + // if it's an out value, the data is already detached + // so the const cast is safe. + const_cast( data ), + sizeof( int ), + SQLT_INT, indPtr, 0, 0, 0, 0, OCI_DEFAULT ); + break; + case QVariant::UInt: + r = OCIBindByPos( sql, hbnd, err, + pos + 1, + // if it's an out value, the data is already detached + // so the const cast is safe. + const_cast( data ), + sizeof( uint ), + SQLT_UIN, indPtr, 0, 0, 0, 0, OCI_DEFAULT ); + break; + case QVariant::LongLong: + { + QByteArray ba = qMakeOCINumber( val.toLongLong(), err ); + r = OCIBindByPos( sql, hbnd, err, + pos + 1, + ba.data(), + ba.size(), + SQLT_VNU, indPtr, 0, 0, 0, 0, OCI_DEFAULT ); + tmpStorage.append( ba ); + break; + } + case QVariant::ULongLong: + { + QByteArray ba = qMakeOCINumber( val.toULongLong(), err ); + r = OCIBindByPos( sql, hbnd, err, + pos + 1, + ba.data(), + ba.size(), + SQLT_VNU, indPtr, 0, 0, 0, 0, OCI_DEFAULT ); + tmpStorage.append( ba ); + break; + } + case QVariant::Double: + r = OCIBindByPos( sql, hbnd, err, + pos + 1, + // if it's an out value, the data is already detached + // so the const cast is safe. + const_cast( data ), + sizeof( double ), + SQLT_FLT, indPtr, 0, 0, 0, 0, OCI_DEFAULT ); + break; + case QVariant::UserType: + if ( val.canConvert() && !isOutValue( pos ) ) + { + try + { + if ( !geometryObj ) + { + OCI_VERIFY( OCIObjectNew( env, err, svc, OCI_TYPECODE_OBJECT, geometryTDO, ( dvoid * ) 0, OCI_DURATION_SESSION, 1, ( dvoid ** ) &geometryObj ) ); + if ( !geometryObj ) + { + throw OCI_ERROR; + } + + OCI_VERIFY( OCIObjectGetInd( env, err, geometryObj, ( void ** ) &geometryInd ) ); + if ( !geometryInd ) + { + throw OCI_ERROR; + } + } + + OCI_VERIFY( OCIBindByPos( sql, hbnd, err, pos + 1, 0, 0, SQLT_NTY, indPtr, 0, 0, 0, 0, OCI_DEFAULT ) ); + OCI_VERIFY( OCIBindObject( *hbnd, err, geometryTDO, ( dvoid ** )&geometryObj, 0, ( dvoid ** ) &geometryInd, 0 ) ); + + const QOCISpatialGeometry &g = qvariant_cast( val ); + + int n; + OCI_VERIFY( OCICollSize( env, err, geometryObj->elem_info, &n ) ); + OCI_VERIFY( OCICollTrim( env, err, n, geometryObj->elem_info ) ); + + OCI_VERIFY( OCICollSize( env, err, geometryObj->ordinates, &n ) ); + OCI_VERIFY( OCICollTrim( env, err, n, geometryObj->ordinates ) ); + + if ( g.isNull ) + { + geometryInd->_atomic = OCI_IND_NULL; + } + else + { + geometryInd->_atomic = OCI_IND_NOTNULL; + geometryInd->gtype = g.gtype < 0 ? OCI_IND_NULL : OCI_IND_NOTNULL; + geometryInd->srid = g.srid < 0 ? OCI_IND_NULL : OCI_IND_NOTNULL; + + OCI_VERIFY( OCINumberFromInt( err, &g.gtype, sizeof( int ), OCI_NUMBER_SIGNED, &geometryObj->gtype ) ); + OCI_VERIFY( OCINumberFromInt( err, &g.srid, sizeof( int ), OCI_NUMBER_SIGNED, &geometryObj->srid ) ); + + if ( SDO_GTYPE_TT( g.gtype ) == gtPoint ) + { + geometryInd->point._atomic = OCI_IND_NOTNULL; + geometryInd->point.x = OCI_IND_NOTNULL; + geometryInd->point.y = OCI_IND_NOTNULL; + geometryInd->point.z = OCI_IND_NOTNULL; + geometryInd->elem_info = OCI_IND_NULL; + geometryInd->ordinates = OCI_IND_NULL; + + OCI_VERIFY( OCINumberFromReal( err, &g.x, sizeof( double ), &geometryObj->point.x ) ); + OCI_VERIFY( OCINumberFromReal( err, &g.y, sizeof( double ), &geometryObj->point.y ) ); + OCI_VERIFY( OCINumberFromReal( err, &g.z, sizeof( double ), &geometryObj->point.z ) ); + } + else + { + geometryInd->point._atomic = OCI_IND_NULL; + geometryInd->elem_info = g.eleminfo.size() == 0 ? OCI_IND_NULL : OCI_IND_NOTNULL; + geometryInd->ordinates = g.ordinates.size() == 0 ? OCI_IND_NULL : OCI_IND_NOTNULL; + + foreach ( int e, g.eleminfo ) + { + OCINumber n; + OCI_VERIFY( OCINumberFromInt( err, &e, sizeof( int ), OCI_NUMBER_SIGNED, &n ) ); + OCI_VERIFY( OCICollAppend( env, err, &n, 0, geometryObj->elem_info ) ); + } + + foreach ( double o, g.ordinates ) + { + OCINumber n; + OCI_VERIFY( OCINumberFromReal( err, &o, sizeof( double ), &n ) ); + OCI_VERIFY( OCICollAppend( env, err, &n, 0, geometryObj->ordinates ) ); + } + } + } + } + catch ( int e ) + { + r = e; + } + } + else if ( val.canConvert() && !isOutValue( pos ) ) + { + // use a const pointer to prevent a detach + const QOCISpatialRowIdPointer rptr = qvariant_cast( val ); + r = OCIBindByPos( sql, hbnd, err, + pos + 1, + // it's an IN value, so const_cast is ok + const_cast( &rptr->id ), + -1, + SQLT_RDD, indPtr, 0, 0, 0, 0, OCI_DEFAULT ); + } + else + { + qWarning( "Unknown bind variable" ); + r = OCI_ERROR; + } + break; + case QVariant::String: + { + const QString s = val.toString(); + if ( isBinaryValue( pos ) ) + { + r = OCIBindByPos( sql, hbnd, err, + pos + 1, + const_cast( s.utf16() ), + s.length() * sizeof( QChar ), + SQLT_LNG, indPtr, 0, 0, 0, 0, OCI_DEFAULT ); + break; + } + else if ( !isOutValue( pos ) ) + { + // don't detach the string + r = OCIBindByPos( sql, hbnd, err, + pos + 1, + // safe since oracle doesn't touch OUT values + const_cast( s.utf16() ), + ( s.length() + 1 ) * sizeof( QChar ), + SQLT_STR, indPtr, 0, 0, 0, 0, OCI_DEFAULT ); + if ( r == OCI_SUCCESS ) + setCharset( *hbnd, OCI_HTYPE_BIND ); + break; + } + } // fall through for OUT values + default: + { + const QString s = val.toString(); + // create a deep-copy + QByteArray ba( reinterpret_cast( s.utf16() ), ( s.length() + 1 ) * sizeof( QChar ) ); + if ( isOutValue( pos ) ) + { + ba.reserve(( s.capacity() + 1 ) * sizeof( QChar ) ); + *tmpSize = ba.size(); + r = OCIBindByPos( sql, hbnd, err, + pos + 1, + ba.data(), + ba.capacity(), + SQLT_STR, indPtr, tmpSize, 0, 0, 0, OCI_DEFAULT ); + } + else + { + r = OCIBindByPos( sql, hbnd, err, + pos + 1, + ba.data(), + ba.size(), + SQLT_STR, indPtr, 0, 0, 0, 0, OCI_DEFAULT ); + } + if ( r == OCI_SUCCESS ) + setCharset( *hbnd, OCI_HTYPE_BIND ); + tmpStorage.append( ba ); + break; + } // default case + } // switch + if ( r != OCI_SUCCESS ) + qOraWarning( "bind failed: ", err ); + return r; +} + +int QOCISpatialResultPrivate::bindValues( QVector &values, IndicatorArray &indicators, + SizeArray &tmpSizes, QList &tmpStorage ) +{ + ENTER + int r = OCI_SUCCESS; + for ( int i = 0; i < values.count(); ++i ) + { + if ( isOutValue( i ) ) + values[i].detach(); + const QVariant &val = values.at( i ); + + OCIBind * hbnd = 0; // Oracle handles these automatically + sb2 *indPtr = &indicators[i]; + *indPtr = val.isNull() ? -1 : 0; + + bindValue( sql, &hbnd, err, i, val, indPtr, &tmpSizes[i], tmpStorage ); + } + return r; +} + +// will assign out value and remove its temp storage. +static void qOraOutValue( QVariant &value, QList &storage, OCIError* err ) +{ + ENTER + switch ( value.type() ) + { + case QVariant::Time: + value = qMakeDate( storage.takeFirst() ).time(); + break; + case QVariant::Date: + value = qMakeDate( storage.takeFirst() ).date(); + break; + case QVariant::DateTime: + value = qMakeDate( storage.takeFirst() ); + break; + case QVariant::LongLong: + value = qMakeLongLong( storage.takeFirst(), err ); + break; + case QVariant::ULongLong: + value = qMakeULongLong( storage.takeFirst(), err ); + break; + case QVariant::String: + value = QString( + reinterpret_cast( storage.takeFirst().constData() ) ); + break; + default: + break; //nothing + } +} + +void QOCISpatialResultPrivate::outValues( QVector &values, IndicatorArray &indicators, + QList &tmpStorage ) +{ + ENTER + for ( int i = 0; i < values.count(); ++i ) + { + + if ( !isOutValue( i ) ) + continue; + + qOraOutValue( values[i], tmpStorage, err ); + + QVariant::Type typ = values.at( i ).type(); + if ( indicators[i] == -1 ) // NULL + values[i] = QVariant( typ ); + else + values[i] = QVariant( typ, values.at( i ).constData() ); + } +} + + +struct QOCISpatialDriverPrivate +{ + QOCISpatialDriverPrivate(); + + OCIEnv *env; + OCISvcCtx *svc; + OCIServer *srvhp; + OCISession *authp; + OCIError *err; + bool transaction; + int serverVersion; + ub4 prefetchRows; + ub2 prefetchMem; + QString user; + + OCIType *geometryTDO; + + void allocErrorHandle(); + OCIType *tdo( QString type ); +}; + +QOCISpatialDriverPrivate::QOCISpatialDriverPrivate() + : env( 0 ) + , svc( 0 ) + , srvhp( 0 ) + , authp( 0 ) + , err( 0 ) + , transaction( false ) + , serverVersion( -1 ) + , prefetchRows( -1 ) + , prefetchMem( QOCISPATIAL_PREFETCH_MEM ) + , geometryTDO( 0 ) +{ + ENTER +} + +void QOCISpatialDriverPrivate::allocErrorHandle() +{ + ENTER + int r = OCIHandleAlloc( env, + reinterpret_cast( &err ), + OCI_HTYPE_ERROR, + 0, + 0 ); + if ( r != OCI_SUCCESS ) + qWarning( "QOCISpatialDriver: unable to allocate error handle" ); +} + +OCIType *QOCISpatialDriverPrivate::tdo( QString type ) +{ + OCIParam *paramp = 0; + OCIRef *type_ref = 0; + OCIType *tdo = 0; + OCIDescribe *dschp = 0; + + try + { + OCI_VERIFY( OCIHandleAlloc( env, ( void** ) & dschp, OCI_HTYPE_DESCRIBE, 0, 0 ) ); + OCI_VERIFY( OCIDescribeAny( svc, err, ( dvoid * ) type.utf16(), type.length() * sizeof( QChar ), OCI_OTYPE_NAME, OCI_DEFAULT, OCI_PTYPE_TYPE, dschp ) ); + OCI_VERIFY( OCIAttrGet( dschp, OCI_HTYPE_DESCRIBE, ¶mp, 0, OCI_ATTR_PARAM, err ) ); + OCI_VERIFY( OCIAttrGet( paramp, OCI_DTYPE_PARAM, &type_ref, 0, OCI_ATTR_REF_TDO, err ) ); + OCI_VERIFY( OCIObjectPin( env, err, type_ref, 0, OCI_PIN_ANY, OCI_DURATION_SESSION, OCI_LOCK_NONE, ( dvoid ** ) &tdo ) ); + } + catch ( int r ) + { + return 0; + } + + return tdo; +} + +struct OraFieldInfo +{ + QString name; + QVariant::Type type; + ub1 oraIsNull; + ub4 oraType; + sb1 oraScale; + ub4 oraLength; // size in bytes + ub4 oraFieldLength; // amount of characters + sb2 oraPrecision; + QString oraTypeName; + OCIType *oraOCIType; +}; + +QString qOraWarn( OCIError *err, int *errorCode ) +{ + sb4 errcode; + text errbuf[1024]; + errbuf[0] = 0; + errbuf[1] = 0; + + OCIErrorGet( err, + 1, + 0, + &errcode, + errbuf, + sizeof( errbuf ), + OCI_HTYPE_ERROR ); + if ( errorCode ) + *errorCode = errcode; + return QString( reinterpret_cast( errbuf ) ); +} + +void qOraWarningAt( const char* msg, OCIError *err, const char *function, int line ) +{ +#ifdef QOCISPATIAL_DEBUG + qWarning( "%s: %s %s at %d", function, msg, qPrintable( qOraWarn( err ) ), line ); +#else + Q_UNUSED( msg ); + Q_UNUSED( err ); + Q_UNUSED( function ); + Q_UNUSED( line ); +#endif +} + +static int qOraErrorNumber( OCIError *err ) +{ + ENTER + sb4 errcode; + OCIErrorGet( err, + 1, + 0, + &errcode, + 0, + 0, + OCI_HTYPE_ERROR ); + return errcode; +} + +QSqlError qMakeError( const QString& errString, QSqlError::ErrorType type, OCIError *err ) +{ + ENTER + int errorCode = 0; + const QString oraErrorString = qOraWarn( err, &errorCode ); + return QSqlError( errString, oraErrorString, type, errorCode ); +} + +QVariant::Type qDecodeOCIType( const QString& ocitype, QSql::NumericalPrecisionPolicy precisionPolicy ) +{ + ENTER + QVariant::Type type = QVariant::Invalid; + qDebug( "qDecodeOCIType(ocitype=%s, precisionPolicy=%d)\n", ocitype.toLocal8Bit().constData(), precisionPolicy ); + if ( ocitype == QLatin1String( "VARCHAR2" ) || ocitype == QLatin1String( "VARCHAR" ) + || ocitype.startsWith( QLatin1String( "INTERVAL" ) ) + || ocitype == QLatin1String( "CHAR" ) || ocitype == QLatin1String( "NVARCHAR2" ) + || ocitype == QLatin1String( "NCHAR" ) ) + type = QVariant::String; + else if ( ocitype == QLatin1String( "NUMBER" ) + || ocitype == QLatin1String( "FLOAT" ) + || ocitype == QLatin1String( "BINARY_FLOAT" ) + || ocitype == QLatin1String( "BINARY_DOUBLE" ) ) + { + switch ( precisionPolicy ) + { + case QSql::LowPrecisionInt32: + type = QVariant::Int; + break; + case QSql::LowPrecisionInt64: + type = QVariant::LongLong; + break; + case QSql::LowPrecisionDouble: + type = QVariant::Double; + break; + case QSql::HighPrecision: + default: + type = QVariant::String; + break; + } + } + else if ( ocitype == QLatin1String( "LONG" ) || ocitype == QLatin1String( "NCLOB" ) + || ocitype == QLatin1String( "CLOB" ) ) + type = QVariant::ByteArray; + else if ( ocitype == QLatin1String( "RAW" ) || ocitype == QLatin1String( "LONG RAW" ) + || ocitype == QLatin1String( "ROWID" ) || ocitype == QLatin1String( "BLOB" ) + || ocitype == QLatin1String( "CFILE" ) || ocitype == QLatin1String( "BFILE" ) ) + type = QVariant::ByteArray; + else if ( ocitype == QLatin1String( "DATE" ) || ocitype.startsWith( QLatin1String( "TIME" ) ) ) + type = QVariant::DateTime; + else if ( ocitype == QLatin1String( "UNDEFINED" ) ) + type = QVariant::Invalid; + if ( type == QVariant::Invalid ) + qWarning( "qDecodeOCIType: unknown type: %s", ocitype.toLocal8Bit().constData() ); + return type; +} + +QVariant::Type qDecodeOCIType( int ocitype, QSql::NumericalPrecisionPolicy precisionPolicy ) +{ + ENTER + QVariant::Type type = QVariant::Invalid; + qDebug( "qDecodeOCIType(ocitype=%d, precisionPolicy=%d)\n", ocitype, precisionPolicy ); + switch ( ocitype ) + { + case SQLT_STR: + case SQLT_VST: + case SQLT_CHR: + case SQLT_AFC: + case SQLT_VCS: + case SQLT_AVC: + case SQLT_RDD: + case SQLT_LNG: +#ifdef SQLT_INTERVAL_YM + case SQLT_INTERVAL_YM: +#endif +#ifdef SQLT_INTERVAL_DS + case SQLT_INTERVAL_DS: +#endif + type = QVariant::String; + break; + case SQLT_INT: + type = QVariant::Int; + break; + case SQLT_FLT: + case SQLT_NUM: + case SQLT_VNU: + case SQLT_UIN: + case SQLT_IBDOUBLE: + case SQLT_IBFLOAT: + switch ( precisionPolicy ) + { + case QSql::LowPrecisionInt32: + type = QVariant::Int; + break; + case QSql::LowPrecisionInt64: + type = QVariant::LongLong; + break; + case QSql::LowPrecisionDouble: + type = QVariant::Double; + break; + case QSql::HighPrecision: + default: + type = QVariant::String; + break; + } + break; + case SQLT_VBI: + case SQLT_BIN: + case SQLT_LBI: + case SQLT_LVC: + case SQLT_LVB: + case SQLT_BLOB: + case SQLT_CLOB: + case SQLT_FILE: + case SQLT_NTY: + case SQLT_REF: + case SQLT_RID: + type = QVariant::ByteArray; + break; + case SQLT_DAT: + case SQLT_ODT: +#ifdef SQLT_TIMESTAMP + case SQLT_TIMESTAMP: + case SQLT_TIMESTAMP_TZ: + case SQLT_TIMESTAMP_LTZ: +#endif + type = QVariant::DateTime; + break; + default: + type = QVariant::Invalid; + qWarning( "qDecodeOCIType: unknown OCI datatype: %d", ocitype ); + break; + } + return type; +} + +static QSqlField qFromOraInf( const OraFieldInfo &ofi ) +{ + ENTER + QSqlField f( ofi.name, ofi.type ); + f.setRequired( ofi.oraIsNull == 0 ); + + if ( ofi.type == QVariant::String && ofi.oraType != SQLT_NUM && ofi.oraType != SQLT_VNU ) + f.setLength( ofi.oraFieldLength ); + else + f.setLength( ofi.oraPrecision == 0 ? 38 : int( ofi.oraPrecision ) ); + + f.setPrecision( ofi.oraScale ); + f.setSqlType( int( ofi.oraType ) ); + return f; +} + +/*! + \internal + + Convert QDateTime to the internal Oracle DATE format NB! + It does not handle BCE dates. +*/ +QByteArray qMakeOraDate( const QDateTime& dt ) +{ + ENTER + QByteArray ba; + ba.resize( 7 ); + int year = dt.date().year(); + ba[0] = ( year / 100 ) + 100; // century + ba[1] = ( year % 100 ) + 100; // year + ba[2] = dt.date().month(); + ba[3] = dt.date().day(); + ba[4] = dt.time().hour() + 1; + ba[5] = dt.time().minute() + 1; + ba[6] = dt.time().second() + 1; + return ba; +} + +/*! + \internal + + Convert qlonglong to the internal Oracle OCINumber format. + */ +QByteArray qMakeOCINumber( const qlonglong& ll, OCIError* err ) +{ + ENTER + QByteArray ba( sizeof( OCINumber ), 0 ); + + OCINumberFromInt( err, + &ll, + sizeof( qlonglong ), + OCI_NUMBER_SIGNED, + reinterpret_cast( ba.data() ) ); + return ba; +} + +/*! + \internal + + Convert qulonglong to the internal Oracle OCINumber format. + */ +QByteArray qMakeOCINumber( const qulonglong& ull, OCIError* err ) +{ + ENTER + QByteArray ba( sizeof( OCINumber ), 0 ); + + OCINumberFromInt( err, + &ull, + sizeof( qlonglong ), + OCI_NUMBER_UNSIGNED, + reinterpret_cast( ba.data() ) ); + return ba; +} + +qlonglong qMakeLongLong( const char* ociNumber, OCIError* err ) +{ + ENTER + qlonglong qll = 0; + OCINumberToInt( err, reinterpret_cast( ociNumber ), sizeof( qlonglong ), + OCI_NUMBER_SIGNED, &qll ); + return qll; +} + +qulonglong qMakeULongLong( const char* ociNumber, OCIError* err ) +{ + ENTER + qulonglong qull = 0; + OCINumberToInt( err, reinterpret_cast( ociNumber ), sizeof( qulonglong ), + OCI_NUMBER_UNSIGNED, &qull ); + return qull; +} + +QDateTime qMakeDate( const char* oraDate ) +{ + ENTER + int century = uchar( oraDate[0] ); + if ( century >= 100 ) + { + int year = uchar( oraDate[1] ); + year = (( century - 100 ) * 100 ) + ( year - 100 ); + int month = oraDate[2]; + int day = oraDate[3]; + int hour = oraDate[4] - 1; + int min = oraDate[5] - 1; + int sec = oraDate[6] - 1; + return QDateTime( QDate( year, month, day ), QTime( hour, min, sec ) ); + } + return QDateTime(); +} + +class QOCISpatialCols +{ + public: + QOCISpatialCols( int size, QOCISpatialResultPrivate* dp ); + ~QOCISpatialCols(); + int readPiecewise( QVector &values, int index = 0 ); + int readLOBs( QVector &values, int index = 0 ); + int fieldFromDefine( OCIDefine* d ); + void getValues( QVector &v, int index ); + inline int size() { return fieldInf.size(); } + static bool execBatch( QOCISpatialResultPrivate *d, QVector &boundValues, bool arrayBind ); + QSqlRecord rec; + + private: + char* create( int position, int size ); + OCILobLocator ** createLobLocator( int position, OCIEnv* env ); + OraFieldInfo qMakeOraField( const QOCISpatialResultPrivate* p, OCIParam* param ) const; + + class OraFieldInf + { + public: + OraFieldInf(): data( 0 ), len( 0 ), ind( 0 ), typ( QVariant::Invalid ), oraType( 0 ), def( 0 ), lob( 0 ) + {} + ~OraFieldInf(); + char *data; + int len; + sb2 ind; + QVariant::Type typ; + ub4 oraType; + OCIDefine *def; + OCILobLocator *lob; + QString oraTypeName; + }; + + bool convertToWkb( QVariant &v ); + bool getValue( OCINumber *num, unsigned int &value ); + bool getValue( OCINumber *num, int &value ); + bool getValue( OCINumber *num, double &value ); + bool getArraySize( OCIColl *coll, int &nSize ); + bool getArrayItem( OCIArray *coll, int elem, unsigned int &item ); + bool getArrayItem( OCIArray *coll, int elem, int &item ); + bool getArrayItem( OCIArray *coll, int elem, double &item ); + bool getElemInfoElem( int elem, int nElems, int nOrds, int &startOffset, int &endOffset, int &etype, int &interpretation ); + static int byteorder() { static char littleEndian = htonl( 1 ) != 1; return littleEndian; } + +#ifdef QOCISPATIAL_DEBUG + void dumpArrays( int nElems, int nOrds ); +#endif + + QVector fieldInf; + const QOCISpatialResultPrivate *const d; +}; + +QOCISpatialCols::OraFieldInf::~OraFieldInf() +{ + ENTER + delete [] data; + if ( lob ) + { + int r = OCIDescriptorFree( lob, OCI_DTYPE_LOB ); + if ( r != OCI_SUCCESS ) + qWarning( "QOCISpatialCols: Cannot free LOB descriptor" ); + } +} + +QOCISpatialCols::QOCISpatialCols( int size, QOCISpatialResultPrivate* dp ) + : fieldInf( size ), d( dp ) +{ + ENTER + ub4 dataSize = 0; + OCIDefine* dfn = 0; + int r; + + OCIParam* param = 0; + sb4 parmStatus = 0; + ub4 count = 1; + int idx = 0; + parmStatus = OCIParamGet( d->sql, + OCI_HTYPE_STMT, + d->err, + reinterpret_cast( ¶m ), + count ); + + if ( parmStatus != OCI_SUCCESS ) + { + qOraWarning( "OCIParamGet failed: ", d->err ); + } + + while ( parmStatus == OCI_SUCCESS ) + { + OraFieldInfo ofi = qMakeOraField( d, param ); + if ( ofi.oraType == SQLT_RDD ) + dataSize = 50; +#ifdef SQLT_INTERVAL_YM +#ifdef SQLT_INTERVAL_DS + else if ( ofi.oraType == SQLT_INTERVAL_YM || ofi.oraType == SQLT_INTERVAL_DS ) + // since we are binding interval datatype as string, + // we are not interested in the number of bytes but characters. + dataSize = 50; // magic number +#endif //SQLT_INTERVAL_DS +#endif //SQLT_INTERVAL_YM + else if ( ofi.oraType == SQLT_NUM || ofi.oraType == SQLT_VNU ) + { + if ( ofi.oraPrecision > 0 ) + dataSize = ( ofi.oraPrecision + 1 ) * sizeof( utext ); + else + dataSize = ( 38 + 1 ) * sizeof( utext ); + } + else + dataSize = ofi.oraLength; + + fieldInf[idx].typ = ofi.type; + fieldInf[idx].oraType = ofi.oraType; + fieldInf[idx].oraTypeName = ofi.oraTypeName; + rec.append( qFromOraInf( ofi ) ); + + qDebug() << "ofi.type:" << QVariant::typeToName( ofi.type ); + switch ( ofi.type ) + { + case QVariant::DateTime: + r = OCIDefineByPos( d->sql, + &dfn, + d->err, + count, + create( idx, dataSize + 1 ), + dataSize + 1, + SQLT_DAT, + &( fieldInf[idx].ind ), + 0, 0, OCI_DEFAULT ); + break; + case QVariant::Double: + r = OCIDefineByPos( d->sql, + &dfn, + d->err, + count, + create( idx, sizeof( double ) - 1 ), + sizeof( double ), + SQLT_FLT, + &( fieldInf[idx].ind ), + 0, 0, OCI_DEFAULT ); + break; + case QVariant::Int: + r = OCIDefineByPos( d->sql, + &dfn, + d->err, + count, + create( idx, sizeof( qint32 ) - 1 ), + sizeof( qint32 ), + SQLT_INT, + &( fieldInf[idx].ind ), + 0, 0, OCI_DEFAULT ); + break; + case QVariant::LongLong: + r = OCIDefineByPos( d->sql, + &dfn, + d->err, + count, + create( idx, sizeof( OCINumber ) ), + sizeof( OCINumber ), + SQLT_VNU, + &( fieldInf[idx].ind ), + 0, 0, OCI_DEFAULT ); + break; + case QVariant::ByteArray: + // RAW and LONG RAW fields can't be bound to LOB locators + qDebug() << "ofi.oraType:" << ofi.oraType; + if ( ofi.oraType == SQLT_BIN ) + { + qDebug( "binding SQLT_BIN" ); + r = OCIDefineByPos( d->sql, + &dfn, + d->err, + count, + create( idx, dataSize ), + dataSize, + SQLT_BIN, + &( fieldInf[idx].ind ), + 0, 0, OCI_DYNAMIC_FETCH ); + } + else if ( ofi.oraType == SQLT_LBI ) + { + qDebug( "binding SQLT_LBI" ); + r = OCIDefineByPos( d->sql, + &dfn, + d->err, + count, + 0, + SB4MAXVAL, + SQLT_LBI, + &( fieldInf[idx].ind ), + 0, 0, OCI_DYNAMIC_FETCH ); + } + else if ( ofi.oraType == SQLT_CLOB ) + { + qDebug( "binding SQLT_CLOB" ); + r = OCIDefineByPos( d->sql, + &dfn, + d->err, + count, + createLobLocator( idx, d->env ), + -1, + SQLT_CLOB, + &( fieldInf[idx].ind ), + 0, 0, OCI_DEFAULT ); + } + else if ( ofi.oraType == SQLT_NTY && ofi.oraTypeName == "SDO_GEOMETRY" ) + { + qDebug( "binding SQLT_NTY SDO_GEOMETRY" ); + r = OCIDefineByPos( d->sql, + &dfn, + d->err, + count, + 0, + 0, + SQLT_NTY, + 0, + 0, + 0, + OCI_DEFAULT ); + + if ( r == OCI_SUCCESS ) + { + qDebug( "define object" ); + r = OCIDefineObject( dfn, + d->err, + ofi.oraOCIType, + ( void** ) & dp->sdoobj, 0, + ( void** ) & dp->sdoind, 0 ); + } + else + { + qOraWarning( "OCIDefineByPos failed: ", d->err ); + } + } + else + { + qDebug( "binding SQLT_BLOB" ); + r = OCIDefineByPos( d->sql, + &dfn, + d->err, + count, + createLobLocator( idx, d->env ), + -1, + SQLT_BLOB, + &( fieldInf[idx].ind ), + 0, 0, OCI_DEFAULT ); + } + break; + case QVariant::String: + if ( ofi.oraType == SQLT_LNG ) + { + qDebug( "binding SQLT_LNG" ); + r = OCIDefineByPos( d->sql, + &dfn, + d->err, + count, + 0, + SB4MAXVAL, + SQLT_LNG, + &( fieldInf[idx].ind ), + 0, 0, OCI_DYNAMIC_FETCH ); + } + else + { + dataSize += dataSize + sizeof( QChar ); + qDebug( "OCIDefineByPosStr(%d): %d", count, dataSize ); + r = OCIDefineByPos( d->sql, + &dfn, + d->err, + count, + create( idx, dataSize ), + dataSize, + SQLT_STR, + &( fieldInf[idx].ind ), + 0, 0, OCI_DEFAULT ); + if ( r == OCI_SUCCESS ) + d->setCharset( dfn, OCI_HTYPE_DEFINE ); + } + break; + default: + // this should make enough space even with character encoding + dataSize = ( dataSize + 1 ) * sizeof( utext ) ; + qDebug( "OCIDefineByPosDef(%d): %d", count, dataSize ); + r = OCIDefineByPos( d->sql, + &dfn, + d->err, + count, + create( idx, dataSize ), + dataSize + 1, + SQLT_STR, + &( fieldInf[idx].ind ), + 0, 0, OCI_DEFAULT ); + break; + } + + if ( r != OCI_SUCCESS ) + qOraWarning( "bind failed: ", d->err ); + + fieldInf[idx].def = dfn; + ++count; + ++idx; + parmStatus = OCIParamGet( d->sql, + OCI_HTYPE_STMT, + d->err, + reinterpret_cast( ¶m ), + count ); + } +} + +QOCISpatialCols::~QOCISpatialCols() +{ + ENTER +} + +char* QOCISpatialCols::create( int position, int size ) +{ + ENTER + char* c = new char[size+1]; + // Oracle may not fill fixed width fields + memset( c, 0, size + 1 ); + fieldInf[position].data = c; + fieldInf[position].len = size; + return c; +} + +OCILobLocator **QOCISpatialCols::createLobLocator( int position, OCIEnv* env ) +{ + ENTER + OCILobLocator *& lob = fieldInf[position].lob; + int r = OCIDescriptorAlloc( env, + reinterpret_cast( &lob ), + OCI_DTYPE_LOB, + 0, + 0 ); + if ( r != OCI_SUCCESS ) + { + qWarning( "QOCISpatialCols: Cannot create LOB locator" ); + lob = 0; + } + return &lob; +} + +int QOCISpatialCols::readPiecewise( QVector &values, int index ) +{ + ENTER + qDebug() << "readPiecewise( index =" << index << " )"; + OCIDefine* dfn; + ub4 typep; + ub1 in_outp; + ub4 iterp; + ub4 idxp; + ub1 piecep; + sword status; + text col [QOCISPATIAL_DYNAMIC_CHUNK_SIZE+1]; + int fieldNum = -1; + int r = 0; + bool nullField; + + do + { + r = OCIStmtGetPieceInfo( d->sql, d->err, reinterpret_cast( &dfn ), &typep, + &in_outp, &iterp, &idxp, &piecep ); + if ( r != OCI_SUCCESS ) + qOraWarning( "unable to get piece info:", d->err ); + fieldNum = fieldFromDefine( dfn ); + bool isStringField = fieldInf.at( fieldNum ).oraType == SQLT_LNG; + ub4 chunkSize = QOCISPATIAL_DYNAMIC_CHUNK_SIZE; + nullField = false; + r = OCIStmtSetPieceInfo( dfn, OCI_HTYPE_DEFINE, + d->err, col, + &chunkSize, piecep, NULL, NULL ); + if ( r != OCI_SUCCESS ) + qOraWarning( "unable to set piece info:", d->err ); + status = OCIStmtFetch( d->sql, d->err, 1, OCI_FETCH_NEXT, OCI_DEFAULT ); + if ( status == -1 ) + { + sb4 errcode; + OCIErrorGet( d->err, 1, 0, &errcode, 0, 0, OCI_HTYPE_ERROR ); + switch ( errcode ) + { + case 1405: /* NULL */ + nullField = true; + break; + default: + qOraWarning( "unable to fetch next:", d->err ); + break; + } + } + if ( status == OCI_NO_DATA ) + break; + if ( nullField || !chunkSize ) + { + fieldInf[fieldNum].ind = -1; + } + else + { + if ( isStringField ) + { + QString str = values.at( fieldNum + index ).toString(); + str += QString( reinterpret_cast( col ), chunkSize / 2 ); + values[fieldNum + index] = str; + fieldInf[fieldNum].ind = 0; + } + else + { + QByteArray ba = values.at( fieldNum + index ).toByteArray(); + int sz = ba.size(); + ba.resize( sz + chunkSize ); + memcpy( ba.data() + sz, reinterpret_cast( col ), chunkSize ); + values[fieldNum + index] = ba; + fieldInf[fieldNum].ind = 0; + } + } + } + while ( status == OCI_SUCCESS_WITH_INFO || status == OCI_NEED_DATA ); + return r; +} + +OraFieldInfo QOCISpatialCols::qMakeOraField( const QOCISpatialResultPrivate* p, OCIParam* param ) const +{ + ENTER + + OraFieldInfo ofi; + ub2 colType( 0 ); + text *colName = 0; + ub4 colNameLen( 0 ); + sb1 colScale( 0 ); + ub2 colLength( 0 ); + ub2 colFieldLength( 0 ); + sb2 colPrecision( 0 ); + ub1 colIsNull( 0 ); + text *colTypeName = 0; + ub4 colTypeNameLen( 0 ); + OCIType *colOCIType = 0; + int r( 0 ); + QVariant::Type type( QVariant::Invalid ); + + r = OCIAttrGet( param, + OCI_DTYPE_PARAM, + &colType, + 0, + OCI_ATTR_DATA_TYPE, + p->err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", p->err ); + + r = OCIAttrGet( param, + OCI_DTYPE_PARAM, + &colName, + &colNameLen, + OCI_ATTR_NAME, + p->err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", p->err ); + + r = OCIAttrGet( param, + OCI_DTYPE_PARAM, + &colLength, + 0, + OCI_ATTR_DATA_SIZE, /* in bytes */ + p->err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", p->err ); + +#ifdef OCI_ATTR_CHAR_SIZE + r = OCIAttrGet( param, + OCI_DTYPE_PARAM, + &colFieldLength, + 0, + OCI_ATTR_CHAR_SIZE, + p->err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", p->err ); +#else + // for Oracle8. + colFieldLength = colLength; +#endif + + r = OCIAttrGet( param, + OCI_DTYPE_PARAM, + &colPrecision, + 0, + OCI_ATTR_PRECISION, + p->err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", p->err ); + + r = OCIAttrGet( param, + OCI_DTYPE_PARAM, + &colScale, + 0, + OCI_ATTR_SCALE, + p->err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", p->err ); + + r = OCIAttrGet( param, + OCI_DTYPE_PARAM, + &colType, + 0, + OCI_ATTR_DATA_TYPE, + p->err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", p->err ); + + qDebug() << "colType:" << colLength; + + r = OCIAttrGet( param, + OCI_DTYPE_PARAM, + &colIsNull, + 0, + OCI_ATTR_IS_NULL, + p->err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", p->err ); + + if ( colType == SQLT_NTY ) + { + qDebug() << "NTY!"; + r = OCIAttrGet( param, + OCI_DTYPE_PARAM, + &colTypeName, + &colTypeNameLen, + OCI_ATTR_TYPE_NAME, + p->err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", p->err ); + + qDebug() << "typename: " << QString( reinterpret_cast( colTypeName ), colTypeNameLen / 2 ); + + OCIRef *typeRef = 0; + +#if 1 + r = OCIAttrGet( param, + OCI_DTYPE_PARAM, + &typeRef, + 0, + OCI_ATTR_REF_TDO, + p->err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", p->err ); +#else + OCIParam *paramp = 0; + OCIDescribe *dschp = 0; + + r = OCIHandleAlloc( d->env, ( void** ) & dschp, OCI_HTYPE_DESCRIBE, 0, 0 ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", d->err ); + + r = OCIDescribeAny( d->svc, d->err, colTypeName, colTypeNameLen, OCI_OTYPE_NAME, OCI_DEFAULT, OCI_PTYPE_TYPE, dschp ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", d->err ); + + r = OCIAttrGet( dschp, OCI_HTYPE_DESCRIBE, ¶mp, 0, OCI_ATTR_PARAM, d->err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", d->err ); + + r = OCIAttrGet( paramp, OCI_DTYPE_PARAM, &typeRef, 0, OCI_ATTR_REF_TDO, d->err ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", d->err ); +#endif + + r = OCIObjectPin( d->env, d->err, typeRef, 0, OCI_PIN_ANY, OCI_DURATION_SESSION, OCI_LOCK_NONE, ( void** ) & colOCIType ); + if ( r != OCI_SUCCESS ) + qOraWarning( "qMakeOraField:", d->err ); + } + + type = qDecodeOCIType( colType, p->q->numericalPrecisionPolicy() ); + + if ( type == QVariant::Int ) + { + if ( colLength == 22 && colPrecision == 0 && colScale == 0 ) + type = QVariant::String; + if ( colScale > 0 ) + type = QVariant::String; + } + + // bind as double if the precision policy asks for it + if ((( colType == SQLT_FLT ) || ( colType == SQLT_NUM ) ) + && ( p->q->numericalPrecisionPolicy() == QSql::LowPrecisionDouble ) ) + { + type = QVariant::Double; + } + + // bind as int32 or int64 if the precision policy asks for it + if (( colType == SQLT_NUM ) || ( colType == SQLT_VNU ) || ( colType == SQLT_UIN ) + || ( colType == SQLT_INT ) ) + { + if ( p->q->numericalPrecisionPolicy() == QSql::LowPrecisionInt64 ) + type = QVariant::LongLong; + else if ( p->q->numericalPrecisionPolicy() == QSql::LowPrecisionInt32 ) + type = QVariant::Int; + } + + if ( colType == SQLT_BLOB ) + colLength = 0; + + // colNameLen is length in bytes + ofi.name = QString( reinterpret_cast( colName ), colNameLen / 2 ); + ofi.type = type; + ofi.oraType = colType; + ofi.oraFieldLength = colFieldLength; + ofi.oraLength = colLength; + ofi.oraScale = colScale; + ofi.oraPrecision = colPrecision; + ofi.oraIsNull = colIsNull; + ofi.oraTypeName = QString( reinterpret_cast( colTypeName ), colTypeNameLen / 2 ); + ofi.oraOCIType = colOCIType; + +#ifdef QOCISPATIAL_DEBUG + qDebug() << "name: " << ofi.name + << "\ntype:" << ofi.type + << "\noraType:" << ofi.oraType + << "\noraFieldLength:" << ofi.oraFieldLength + << "\noraLength:" << ofi.oraLength + << "\noraScale:" << ofi.oraScale + << "\noraPrecision:" << ofi.oraPrecision + << "\noraIsNull:" << ofi.oraIsNull + << "\noraTypeName:" << ofi.oraTypeName + << "\n----------------------\n" + ; +#endif + + return ofi; +} + +struct QOCISpatialBatchColumn +{ + inline QOCISpatialBatchColumn() + : bindh( 0 ), bindAs( 0 ), maxLen( 0 ), recordCount( 0 ), + data( 0 ), lengths( 0 ), indicators( 0 ), maxarr_len( 0 ), curelep( 0 ) {} + + OCIBind* bindh; + ub2 bindAs; + ub4 maxLen; + ub4 recordCount; + char* data; + ub2* lengths; + sb2* indicators; + ub4 maxarr_len; + ub4 curelep; +}; + +struct QOCISpatialBatchCleanupHandler +{ + inline QOCISpatialBatchCleanupHandler( QVector &columns ) + : col( columns ) {} + + ~QOCISpatialBatchCleanupHandler() + { + // deleting storage, length and indicator arrays + for ( int j = 0; j < col.count(); ++j ) + { + delete[] col[j].lengths; + delete[] col[j].indicators; + delete[] col[j].data; + } + } + + QVector &col; +}; + +bool QOCISpatialCols::execBatch( QOCISpatialResultPrivate *d, QVector &boundValues, bool arrayBind ) +{ + ENTER + + int columnCount = boundValues.count(); + if ( boundValues.isEmpty() || columnCount == 0 ) + return false; + +#ifdef QOCISPATIAL_DEBUG + qDebug() << "columnCount:" << columnCount << boundValues; +#endif + + int i; + sword r; + + QVarLengthArray fieldTypes; + for ( i = 0; i < columnCount; ++i ) + { + QVariant::Type tp = boundValues.at( i ).type(); + fieldTypes.append( tp == QVariant::List ? boundValues.at( i ).toList().value( 0 ).type() + : tp ); + } + + QList tmpStorage; + SizeArray tmpSizes( columnCount ); + QVector columns( columnCount ); + QOCISpatialBatchCleanupHandler cleaner( columns ); + + // figuring out buffer sizes + for ( i = 0; i < columnCount; ++i ) + { + + if ( boundValues.at( i ).type() != QVariant::List ) + { + + // not a list - create a deep-copy of the single value + QOCISpatialBatchColumn &singleCol = columns[i]; + singleCol.indicators = new sb2[1]; + *singleCol.indicators = boundValues.at( i ).isNull() ? -1 : 0; + + r = d->bindValue( d->sql, &singleCol.bindh, d->err, i, + boundValues.at( i ), singleCol.indicators, &tmpSizes[i], tmpStorage ); + + if ( r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO ) + { + qOraWarning( "QOCISpatialPrivate::execBatch: unable to bind column:", d->err ); + d->q->setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialResult", + "Unable to bind column for batch execute" ), + QSqlError::StatementError, d->err ) ); + return false; + } + continue; + } + + QOCISpatialBatchColumn &col = columns[i]; + col.recordCount = boundValues.at( i ).toList().count(); + + col.lengths = new ub2[col.recordCount]; + col.indicators = new sb2[col.recordCount]; + col.maxarr_len = col.recordCount; + col.curelep = col.recordCount; + + switch ( fieldTypes[i] ) + { + case QVariant::Time: + case QVariant::Date: + case QVariant::DateTime: + col.bindAs = SQLT_DAT; + col.maxLen = 7; + break; + + case QVariant::Int: + col.bindAs = SQLT_INT; + col.maxLen = sizeof( int ); + break; + + case QVariant::UInt: + col.bindAs = SQLT_UIN; + col.maxLen = sizeof( uint ); + break; + + case QVariant::LongLong: + col.bindAs = SQLT_VNU; + col.maxLen = sizeof( OCINumber ); + break; + + case QVariant::ULongLong: + col.bindAs = SQLT_VNU; + col.maxLen = sizeof( OCINumber ); + break; + + case QVariant::Double: + col.bindAs = SQLT_FLT; + col.maxLen = sizeof( double ); + break; + + case QVariant::UserType: + col.bindAs = SQLT_RDD; + col.maxLen = sizeof( OCIRowid* ); + break; + + case QVariant::String: + { + col.bindAs = SQLT_STR; + for ( uint j = 0; j < col.recordCount; ++j ) + { + uint len; + if ( d->isOutValue( i ) ) + len = boundValues.at( i ).toList().at( j ).toString().capacity() + 1; + else + len = boundValues.at( i ).toList().at( j ).toString().length() + 1; + if ( len > col.maxLen ) + col.maxLen = len; + } + col.maxLen *= sizeof( QChar ); + break; + } + + case QVariant::ByteArray: + default: + { + col.bindAs = SQLT_LBI; + for ( uint j = 0; j < col.recordCount; ++j ) + { + if ( d->isOutValue( i ) ) + col.lengths[j] = boundValues.at( i ).toList().at( j ).toByteArray().capacity(); + else + col.lengths[j] = boundValues.at( i ).toList().at( j ).toByteArray().size(); + if ( col.lengths[j] > col.maxLen ) + col.maxLen = col.lengths[j]; + } + break; + } + } + + col.data = new char[col.maxLen * col.recordCount]; + memset( col.data, 0, col.maxLen * col.recordCount ); + + // we may now populate column with data + for ( uint row = 0; row < col.recordCount; ++row ) + { + const QVariant &val = boundValues.at( i ).toList().at( row ); + + if ( val.isNull() ) + { + columns[i].indicators[row] = -1; + columns[i].lengths[row] = 0; + } + else + { + columns[i].indicators[row] = 0; + char *dataPtr = columns[i].data + ( columns[i].maxLen * row ); + switch ( fieldTypes[i] ) + { + case QVariant::Time: + case QVariant::Date: + case QVariant::DateTime: + { + columns[i].lengths[row] = columns[i].maxLen; + const QByteArray ba = qMakeOraDate( val.toDateTime() ); + Q_ASSERT( ba.size() == int( columns[i].maxLen ) ); + memcpy( dataPtr, ba.constData(), columns[i].maxLen ); + break; + } + case QVariant::Int: + columns[i].lengths[row] = columns[i].maxLen; + *reinterpret_cast( dataPtr ) = val.toInt(); + break; + + case QVariant::UInt: + columns[i].lengths[row] = columns[i].maxLen; + *reinterpret_cast( dataPtr ) = val.toUInt(); + break; + + case QVariant::LongLong: + { + columns[i].lengths[row] = columns[i].maxLen; + const QByteArray ba = qMakeOCINumber( val.toLongLong(), d->err ); + Q_ASSERT( ba.size() == int( columns[i].maxLen ) ); + memcpy( dataPtr, ba.constData(), columns[i].maxLen ); + break; + } + case QVariant::ULongLong: + { + columns[i].lengths[row] = columns[i].maxLen; + const QByteArray ba = qMakeOCINumber( val.toULongLong(), d->err ); + Q_ASSERT( ba.size() == int( columns[i].maxLen ) ); + memcpy( dataPtr, ba.constData(), columns[i].maxLen ); + break; + } + case QVariant::Double: + columns[i].lengths[row] = columns[i].maxLen; + *reinterpret_cast( dataPtr ) = val.toDouble(); + break; + + case QVariant::String: + { + const QString s = val.toString(); + columns[i].lengths[row] = ( s.length() + 1 ) * sizeof( QChar ); + memcpy( dataPtr, s.utf16(), columns[i].lengths[row] ); + break; + } + case QVariant::UserType: + if ( val.canConvert() ) + { + const QOCISpatialRowIdPointer rptr = qvariant_cast( val ); + *reinterpret_cast( dataPtr ) = rptr->id; + columns[i].lengths[row] = 0; + break; + } + case QVariant::ByteArray: + default: + { + const QByteArray ba = val.toByteArray(); + columns[i].lengths[row] = ba.size(); + memcpy( dataPtr, ba.constData(), ba.size() ); + break; + } + } + } + } + + QOCISpatialBatchColumn &bindColumn = columns[i]; + +#ifdef QOCISPATIAL_DEBUG + qDebug( "OCIBindByPos(%p, %p, %p, %d, %p, %d, %d, %p, %p, 0, %d, %p, OCI_DEFAULT)", + d->sql, &bindColumn.bindh, d->err, i + 1, bindColumn.data, + bindColumn.maxLen, bindColumn.bindAs, bindColumn.indicators, bindColumn.lengths, + arrayBind ? bindColumn.maxarr_len : 0, arrayBind ? &bindColumn.curelep : 0 ); + + for ( int ii = 0; ii < ( int )bindColumn.recordCount; ++ii ) + { + qDebug( " record %d: indicator %d, length %d", ii, bindColumn.indicators[ii], + bindColumn.lengths[ii] ); + } +#endif + + + // binding the column + r = OCIBindByPos( + d->sql, &bindColumn.bindh, d->err, i + 1, + bindColumn.data, + bindColumn.maxLen, + bindColumn.bindAs, + bindColumn.indicators, + bindColumn.lengths, + 0, + arrayBind ? bindColumn.maxarr_len : 0, + arrayBind ? &bindColumn.curelep : 0, + OCI_DEFAULT ); + +#ifdef QOCISPATIAL_DEBUG + qDebug( "After OCIBindByPos: r = %d, bindh = %p", r, bindColumn.bindh ); +#endif + + if ( r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO ) + { + qOraWarning( "QOCISpatialPrivate::execBatch: unable to bind column:", d->err ); + d->q->setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialResult", + "Unable to bind column for batch execute" ), + QSqlError::StatementError, d->err ) ); + return false; + } + + r = OCIBindArrayOfStruct( + columns[i].bindh, d->err, + columns[i].maxLen, + sizeof( columns[i].indicators[0] ), + sizeof( columns[i].lengths[0] ), + 0 ); + + if ( r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO ) + { + qOraWarning( "QOCISpatialPrivate::execBatch: unable to bind column:", d->err ); + d->q->setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialResult", + "Unable to bind column for batch execute" ), + QSqlError::StatementError, d->err ) ); + return false; + } + } + + //finally we can execute + r = OCIStmtExecute( d->svc, d->sql, d->err, + arrayBind ? 1 : columns[0].recordCount, + 0, NULL, NULL, + d->transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS ); + + if ( r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO ) + { + qOraWarning( "QOCISpatialPrivate::execBatch: unable to execute batch statement:", d->err ); + d->q->setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialResult", + "Unable to execute batch statement" ), + QSqlError::StatementError, d->err ) ); + return false; + } + + // for out parameters we copy data back to value vector + for ( i = 0; i < columnCount; ++i ) + { + + if ( !d->isOutValue( i ) ) + continue; + + QVariant::Type tp = boundValues.at( i ).type(); + if ( tp != QVariant::List ) + { + qOraOutValue( boundValues[i], tmpStorage, d->err ); + if ( *columns[i].indicators == -1 ) + boundValues[i] = QVariant( tp ); + continue; + } + + QVariantList *list = static_cast( const_cast( boundValues.at( i ).data() ) ); + + char* data = columns[i].data; + for ( uint r = 0; r < columns[i].recordCount; ++r ) + { + + if ( columns[i].indicators[r] == -1 ) + { + ( *list )[r] = QVariant(); + continue; + } + + switch ( columns[i].bindAs ) + { + + case SQLT_DAT: + ( *list )[r] = qMakeDate( data + r * columns[i].maxLen ); + break; + + case SQLT_INT: + ( *list )[r] = *reinterpret_cast( data + r * columns[i].maxLen ); + break; + + case SQLT_UIN: + ( *list )[r] = *reinterpret_cast( data + r * columns[i].maxLen ); + break; + + case SQLT_VNU: + { + switch ( boundValues.at( i ).type() ) + { + case QVariant::LongLong: + ( *list )[r] = qMakeLongLong( data + r * columns[i].maxLen, d->err ); + break; + case QVariant::ULongLong: + ( *list )[r] = qMakeULongLong( data + r * columns[i].maxLen, d->err ); + break; + default: + break; + } + break; + } + + case SQLT_FLT: + ( *list )[r] = *reinterpret_cast( data + r * columns[i].maxLen ); + break; + + case SQLT_STR: + ( *list )[r] = QString( reinterpret_cast( data + + r * columns[i].maxLen ) ); + break; + + default: + ( *list )[r] = QByteArray( data + r * columns[i].maxLen, columns[i].maxLen ); + break; + } + } + } + + d->q->setSelect( false ); + d->q->setAt( QSql::BeforeFirstRow ); + d->q->setActive( true ); + + return true; +} + +template +int qReadLob( T &buf, const QOCISpatialResultPrivate *d, OCILobLocator *lob ) +{ + ENTER + ub1 csfrm; + ub4 amount; + int r; + + // Read this from the database, don't assume we know what it is set to + r = OCILobCharSetForm( d->env, d->err, lob, &csfrm ); + if ( r != OCI_SUCCESS ) + { + qOraWarning( "OCIResultPrivate::readLobs: Couldn't get LOB char set form: ", d->err ); + csfrm = 0; + } + + // Get the length of the LOB (this is in characters) + r = OCILobGetLength( d->svc, d->err, lob, &amount ); + if ( r == OCI_SUCCESS ) + { + if ( amount == 0 ) + { + // Short cut for null LOBs + buf.resize( 0 ); + return OCI_SUCCESS; + } + } + else + { + qOraWarning( "OCIResultPrivate::readLobs: Couldn't get LOB length: ", d->err ); + return r; + } + + // Resize the buffer to hold the LOB contents + buf.resize( amount ); + + // Read the LOB into the buffer + r = OCILobRead( d->svc, + d->err, + lob, + &amount, + 1, + buf.data(), + buf.size() * sz, // this argument is in bytes, not characters + 0, + 0, + // Extract the data from a CLOB in UTF-16 (ie. what QString uses internally) + sz == 1 ? ub2( 0 ) : ub2( QOCISpatialEncoding ), + csfrm ); + + if ( r != OCI_SUCCESS ) + qOraWarning( "OCIResultPrivate::readLOBs: Cannot read LOB: ", d->err ); + + return r; +} + +int QOCISpatialCols::readLOBs( QVector &values, int index ) +{ + ENTER + OCILobLocator *lob; + int r = OCI_SUCCESS; + + for ( int i = 0; i < size(); ++i ) + { + const OraFieldInf &fi = fieldInf.at( i ); + if ( fi.ind == -1 || !( lob = fi.lob ) ) + continue; + + bool isClob = fi.oraType == SQLT_CLOB; + QVariant var; + + if ( isClob ) + { + QString str; + r = qReadLob < QString, sizeof( QChar ) > ( str, d, lob ); + var = str; + } + else + { + QByteArray buf; + r = qReadLob < QByteArray, sizeof( char ) > ( buf, d, lob ); + var = buf; + } + if ( r == OCI_SUCCESS ) + values[index + i] = var; + else + break; + } + return r; +} + +int QOCISpatialCols::fieldFromDefine( OCIDefine* d ) +{ + ENTER + for ( int i = 0; i < fieldInf.count(); ++i ) + { + if ( fieldInf.at( i ).def == d ) + return i; + } + return -1; +} + +bool QOCISpatialCols::getValue( OCINumber *num, unsigned int &value ) +{ + if ( OCINumberToInt( d->err, num, sizeof( unsigned int ), OCI_NUMBER_UNSIGNED, &value ) == OCI_SUCCESS ) + return true; + + qOraWarning( "Couldn't convert number to uint: ", d->err ); + return false; +} + +bool QOCISpatialCols::getValue( OCINumber *num, int &value ) +{ + if ( OCINumberToInt( d->err, num, sizeof( int ), OCI_NUMBER_SIGNED, &value ) == OCI_SUCCESS ) + return true; + + qOraWarning( "Couldn't convert number to int: ", d->err ); + return false; +} + +bool QOCISpatialCols::getValue( OCINumber *num, double &value ) +{ + if ( OCINumberToReal( d->err, num, sizeof( double ), &value ) == OCI_SUCCESS ) + return true; + + qOraWarning( "Couldn't convert number to double: ", d->err ); + return false; +} + +bool QOCISpatialCols::getArraySize( OCIColl *coll, int &nSize ) +{ + if ( OCICollSize( d->env, d->err, coll, &nSize ) == OCI_SUCCESS ) + return true; + + qOraWarning( "Couldn't not get elem_info collection size: ", d->err ); + return false; +} + +bool QOCISpatialCols::getArrayItem( OCIArray *coll, int elem, unsigned int &item ) +{ + OCINumber *num; + boolean exists; + + if ( OCICollGetElem( d->env, d->err, coll, elem, &exists, ( dvoid ** ) &num, 0 ) != OCI_SUCCESS || !exists ) + { + qOraWarning( "Couldn't not get collection item: ", d->err ); + return false; + } + + return getValue( num, item ); +} + +bool QOCISpatialCols::getArrayItem( OCIArray *coll, int elem, int &item ) +{ + OCINumber *num; + boolean exists; + + if ( OCICollGetElem( d->env, d->err, coll, elem, &exists, ( dvoid ** ) &num, 0 ) != OCI_SUCCESS || !exists ) + { + qOraWarning( "Couldn't not get collection item: ", d->err ); + return false; + } + + return getValue( num, item ); +} + +bool QOCISpatialCols::getArrayItem( OCIArray *coll, int elem, double &item ) +{ + OCINumber *num; + boolean exists; + + if ( OCICollGetElem( d->env, d->err, coll, elem, &exists, ( dvoid ** ) &num, 0 ) != OCI_SUCCESS || !exists ) + { + qOraWarning( "Couldn't not get collection item: ", d->err ); + return false; + } + + return getValue( num, item ); +} + +bool QOCISpatialCols::getElemInfoElem( int iElem, int nElems, int nOrds, + int &startOffset, int &endOffset, + int &etype, int &interpretation ) +{ + if ( !getArrayItem( d->sdoobj->elem_info, iElem + 0, startOffset ) || + !getArrayItem( d->sdoobj->elem_info, iElem + 1, etype ) || + !getArrayItem( d->sdoobj->elem_info, iElem + 2, interpretation ) ) + return false; + + if ( iElem + 3 == nElems ) + { + endOffset = nOrds + 1; + } + else if ( !getArrayItem( d->sdoobj->elem_info, iElem + 3, endOffset ) ) + { + qWarning() << "end offset not found"; + return false; + } + + --startOffset; + --endOffset; + + return true; +} + +#ifdef QOCISPATIAL_DEBUG +void QOCISpatialCols::dumpArrays( int nElems, int nOrds ) +{ + qDebug() << "got geometry: sdo_elem_info =" << nElems; + + for ( int i = 0; i < nElems; i++ ) + { + int item; + if ( getArrayItem( d->sdoobj->elem_info, i, item ) ) + qWarning( " %d: %d", i, item ); + else + qWarning( " %d: -", i ); + } + + + qDebug() << "sdo_ordinates =" << nOrds; + + for ( int i = 0; i < nOrds; i++ ) + { + double item; + if ( getArrayItem( d->sdoobj->ordinates, i, item ) ) + qWarning( " %d: %lf", i, item ); + else + qWarning( " %d: -", i ); + } +} +#endif + +bool QOCISpatialCols::convertToWkb( QVariant &v ) +{ + ENTER + + qDebug() << "sdoobj =" << d->sdoobj; + qDebug() << "sdoinf =" << d->sdoind; + if ( d->sdoind ) + qDebug() << "sdoind->_atomic =" << d->sdoind->_atomic; + + v = QVariant( QVariant::ByteArray ); + + if ( !d->sdoobj || !d->sdoind ) + { + qDebug() << "sdoobj or sdoind not set"; + return false; + } + + if ( d->sdoind->_atomic == OCI_IND_NULL ) + { + qDebug() << "geometry is NULL"; + return true; + } + + unsigned int iGType; + if ( !getValue( &d->sdoobj->gtype, iGType ) ) + return false; + + int nDims = SDO_GTYPE_D( iGType ); + int iType = SDO_GTYPE_TT( iGType ); + qDebug() << " d =" << nDims; + qDebug() << " tt =" << iType; + + if ( SDO_GTYPE_L( iGType ) != 0 ) + { + qWarning() << "LRS" << SDO_GTYPE_L( iGType ) << "ignored"; + } + + int iSrid = 0; + if ( d->sdoind->srid == OCI_IND_NOTNULL ) + { + if ( !getValue( &d->sdoobj->srid, iSrid ) ) + return false; + } + + qDebug() << " srid =" << iSrid; + + v = QByteArray(); + QByteArray *ba = static_cast( v.data() ); + union wkbPtr ptr; + + int nElems; + if ( !getArraySize( d->sdoobj->elem_info, nElems ) ) + { + qWarning() << "could not determine element info array size"; + return false; + } + + int nOrds; + if ( !getArraySize( d->sdoobj->ordinates, nOrds ) ) + { + qWarning() << "could not determine ordinate array size"; + return false; + } + +#ifdef QOCISPATIAL_DEBUG + dumpArrays( nElems, nOrds ); +#endif + + Q_ASSERT( nElems % 3 == 0 ); + Q_ASSERT( nOrds % nDims == 0 ); + + if ( iType == gtUnknown ) + { + qWarning() << "unknown geometry"; + return false; + } + + if ( iType == gtPoint && nElems == 0 ) + { + Q_ASSERT( nOrds == 0 ); + + if ( d->sdoind->_atomic != OCI_IND_NOTNULL || + d->sdoind->point.x != OCI_IND_NOTNULL || + d->sdoind->point.y != OCI_IND_NOTNULL ) + { + qDebug() << "null point"; + v = QVariant(); + return true; + } + + double x, y, z = 0.0; + if ( !getValue( &d->sdoobj->point.x, x ) ) + { + qWarning() << "could not convert x ordinate to real"; + return false; + } + + if ( !getValue( &d->sdoobj->point.y, y ) ) + { + qWarning() << "could not convert y ordinate to real"; + return false; + } + + if ( nDims > 2 ) + { + if ( d->sdoind->point.z != OCI_IND_NOTNULL ) + { + qWarning() << "null value in z ordinate"; + return false; + } + + if ( !getValue( &d->sdoobj->point.z, z ) ) + { + qDebug() << "could not convert z ordinate to real"; + return false; + } + } + + ba->resize( 1 + sizeof( int ) + nDims * sizeof( double ) ); + ptr.cPtr = ba->data(); + *ptr.ucPtr++ = byteorder(); + *ptr.iPtr++ = nDims == 2 ? WKBPoint : WKBPoint25D; + *ptr.dPtr++ = x; + *ptr.dPtr++ = y; + if ( nDims > 2 ) + *ptr.dPtr++ = z; + + qDebug() << "returning point"; + return true; + } + + if ( d->sdoind->_atomic != OCI_IND_NOTNULL ) + { + qWarning() << "geometry with sdo_elem_info and non-null sdo_point found."; + return false; + } + + if ( iType == gtPoint || iType == gtMultiPoint ) + { + int nPoints = 0; + + for ( int i = 0; i < nElems; i += 3 ) + { + int startOffset, endOffset, etype, n; + if ( !getElemInfoElem( i, nElems, nOrds, startOffset, endOffset, etype, n ) ) + { + qDebug() << "could not fetch element info" << i; + return false; + } + + if ( etype == 1 && n > 0 ) + nPoints += endOffset - startOffset; + } + + Q_ASSERT( nPoints % nDims == 0 ); + Q_ASSERT( iType == gtMultipoint || nPoints == nDims ); + + int wkbSize = 0; + + if ( nPoints > nDims ) + wkbSize += 1 + 2 * sizeof( int ); + + wkbSize += ( nPoints / nDims ) * ( 1 + sizeof( int ) ) + nPoints * sizeof( double ); + qDebug() << "wkbSize" << wkbSize; + + ba->resize( wkbSize ); + ptr.cPtr = ba->data(); + + if ( nPoints > nDims ) + { + *ptr.ucPtr++ = byteorder(); + *ptr.iPtr++ = nDims == 2 ? WKBMultiPoint : WKBMultiPoint25D; + *ptr.iPtr++ = nPoints / nDims; + } + + for ( int i = 0; i < nElems; i += 3 ) + { + int startOffset, endOffset, etype, n; + if ( !getElemInfoElem( i, nElems, nOrds, startOffset, endOffset, etype, n ) ) + { + qDebug() << "could not fetch element info" << i; + return false; + } + + if ( etype != 1 ) + continue; + + if ( n == 0 ) + { + qDebug() << "point orientation skipped"; + continue; + } + + Q_ASSERT(( endOffset - startOffset ) % nDims == 0 ); + + for ( int j = startOffset, k = 0; j < endOffset; j++, k++ ) + { + if ( k % nDims == 0 ) + { + *ptr.ucPtr++ = byteorder(); + *ptr.iPtr++ = nDims == 2 ? WKBPoint : WKBPoint25D; + } + + double item; + if ( !getArrayItem( d->sdoobj->ordinates, j, item ) ) + { + qWarning() << "cannot read ordinate" << j; + return false; + } + + *ptr.dPtr++ = item; + } + } + + qDebug() << "returning (multi)point"; + return true; + } + + if ( iType == gtLine || iType == gtMultiLine ) + { + Q_ASSERT( nOrds % nDims == 0 ); + + int nPoints = 0; + QVector nLine; + for ( int i = 0; i < nElems; i += 3 ) + { + int startOffset, endOffset, etype, n; + if ( !getElemInfoElem( i, nElems, nOrds, startOffset, endOffset, etype, n ) ) + { + qDebug() << "could not fetch element info" << i; + return false; + } + + if ( etype == 2 && n == 1 ) + { + nPoints += endOffset - startOffset; + nLine << endOffset - startOffset; + } + else + { + qDebug() << "unsupported line element - etype:" << etype << "n:" << n << "skipped"; + } + } + + int wkbSize = 0; + + if ( nLine.size() > 1 ) + wkbSize += 1 + 2 * sizeof( int ); + + wkbSize += nLine.size() * ( 1 + 2 * sizeof( int ) ) + nPoints * sizeof( double ); + qDebug() << "wkbSize" << wkbSize; + + ba->resize( wkbSize ); + ptr.cPtr = ba->data(); + + if ( nLine.size() > 1 ) + { + *ptr.ucPtr++ = byteorder(); + *ptr.iPtr++ = nDims == 2 ? WKBMultiLineString : WKBMultiLineString25D; + *ptr.iPtr++ = nLine.size(); + } + + int iLine = 0; + for ( int i = 0; i < nElems; i++ ) + { + int startOffset, endOffset, etype, n; + if ( !getElemInfoElem( i, nElems, nOrds, startOffset, endOffset, etype, n ) ) + { + qDebug() << "could not fetch element info" << i; + return false; + } + + if ( etype != 2 || n != 1 ) + continue; + + *ptr.ucPtr++ = byteorder(); + *ptr.iPtr++ = nDims == 2 ? WKBLineString : WKBLineString25D; + *ptr.iPtr++ = nLine[iLine++] / nDims; + + for ( int j = startOffset; j < endOffset; j++ ) + { + double item; + if ( !getArrayItem( d->sdoobj->ordinates, j, item ) ) + { + qWarning() << "cannot read ordinate" << j; + return false; + } + *ptr.dPtr++ = item; + } + } + + qDebug() << "returning (multi)line"; + return true; + } + + if ( iType == gtPolygon || iType == gtMultiPolygon ) + { + int nPolygons = 0; + int nPoints = 0; + int nRings = 0; + QVector nPolygonRings; + for ( int i = 0; i < nElems; i += 3 ) + { + int startOffset, endOffset, etype, n; + if ( !getElemInfoElem( i, nElems, nOrds, startOffset, endOffset, etype, n ) ) + { + qDebug() << "could not fetch element info" << i; + return false; + } + + if ( etype % 1000 == 3 && n == 1 ) + { + if ( etype / 1000 == 1 ) + { + nPolygons++; + nPolygonRings << 0; + } + + nRings++; + nPolygonRings[nPolygons-1]++; + nPoints += endOffset - startOffset; + } + else if ( etype % 1000 == 3 && n == 3 ) + { + // rectangle is expanded to a polygon with 5 points + nPolygons++; + nRings++; + nPolygonRings << 1; + nPoints += 5 * nDims; + } + else + { + qDebug() << "unsupported polygon element - etype:" << etype << "n:" << n << "skipped"; + } + } + + Q_ASSERT( nPolygons > 0 ); + Q_ASSERT( nRings.size() >= nPolygons ); + Q_ASSERT( nPoints % nDims == 0 ); + + qDebug() << "polygon" << nPolygons << "rings" << nRings << "points" << nPoints; + + int wkbSize = 0; + + if ( nPolygons > 1 ) + wkbSize += 1 + 2 * sizeof( int ); + + wkbSize += nPolygons * ( 1 + 2 * sizeof( int ) ) + nRings * sizeof( int ) + nPoints * sizeof( double ); + qDebug() << "wkbSize" << wkbSize; + + ba->resize( wkbSize ); + + ptr.cPtr = ba->data(); + if ( nPolygons > 1 ) + { + *ptr.ucPtr++ = byteorder(); + *ptr.iPtr++ = nDims == 2 ? WKBMultiPolygon : WKBMultiPolygon25D; + *ptr.iPtr++ = nPolygons; + } + + int iPolygon = 0; + for ( int i = 0; i < nElems; i += 3 ) + { + int startOffset, endOffset, etype, n; + if ( !getElemInfoElem( i, nElems, nOrds, startOffset, endOffset, etype, n ) ) + { + qDebug() << "could not fetch element info" << i; + return false; + } + + if ( etype % 1000 == 3 && n == 1 ) + { + if ( etype / 1000 == 1 ) + { + // exterior ring starts a new polygon + *ptr.ucPtr++ = byteorder(); + *ptr.iPtr++ = nDims == 2 ? WKBPolygon : WKBPolygon25D; + qDebug() << "ring" << iPolygon << ":" << nPolygonRings[iPolygon]; + *ptr.iPtr++ = nPolygonRings[iPolygon++]; + } + + *ptr.iPtr++ = ( endOffset - startOffset ) / nDims; + for ( int j = startOffset; j < endOffset; j++ ) + { + double item; + if ( !getArrayItem( d->sdoobj->ordinates, j, item ) ) + { + qWarning() << "cannot read ordinate" << j; + return false; + } + *ptr.dPtr++ = item; + } + } + else if ( etype % 1000 == 3 && n == 3 ) + { + // rectangle + *ptr.ucPtr++ = byteorder(); + *ptr.iPtr++ = nDims == 2 ? WKBPolygon : WKBPolygon25D; + qDebug() << "rect (polygon w/ 1 ring w/ 5 points)" << iPolygon << "ordinates" << endOffset - startOffset; + *ptr.iPtr++ = 1; + *ptr.iPtr++ = 5; + + double x0, y0, x1, y1; + + if ( startOffset + nDims + 1 >= endOffset || + !getArrayItem( d->sdoobj->ordinates, startOffset + 0, x0 ) || + !getArrayItem( d->sdoobj->ordinates, startOffset + 1, y0 ) || + !getArrayItem( d->sdoobj->ordinates, startOffset + nDims + 0, x1 ) || + !getArrayItem( d->sdoobj->ordinates, startOffset + nDims + 1, y1 ) ) + { + qWarning() << "less ordinates than expected"; + return false; + } + + *ptr.dPtr++ = x0; + *ptr.dPtr++ = y0; + for ( int j = 2; j < nDims; j++ ) + *ptr.dPtr++ = 0.0; + + *ptr.dPtr++ = x1; + *ptr.dPtr++ = y0; + for ( int j = 2; j < nDims; j++ ) + *ptr.dPtr++ = 0.0; + + *ptr.dPtr++ = x1; + *ptr.dPtr++ = y1; + for ( int j = 2; j < nDims; j++ ) + *ptr.dPtr++ = 0.0; + + *ptr.dPtr++ = x0; + *ptr.dPtr++ = y1; + for ( int j = 2; j < nDims; j++ ) + *ptr.dPtr++ = 0.0; + + *ptr.dPtr++ = x0; + *ptr.dPtr++ = y0; + for ( int j = 2; j < nDims; j++ ) + *ptr.dPtr++ = 0.0; + } + } + + qDebug() << "returning (multi)polygon size" << ba->size(); + return true; + } + + qWarning() << "geometry type" << iType << "not supported"; + return false; +} + +void QOCISpatialCols::getValues( QVector &v, int index ) +{ + ENTER + for ( int i = 0; i < fieldInf.size(); ++i ) + { + qDebug() << "getValues( index =" << index << "i =" << i << " )"; + const OraFieldInf &fld = fieldInf.at( i ); + + if ( fld.ind == -1 ) + { + // got a NULL value + qDebug() << "NULL"; + v[index + i] = QVariant( fld.typ ); + continue; + } + + qDebug() << "oraType:" << fld.oraType; + if ( fld.oraType == SQLT_BIN || fld.oraType == SQLT_LBI || fld.oraType == SQLT_LNG ) + { + qDebug() << "fetching piecewise"; + continue; // already fetched piecewise + } + + switch ( fld.typ ) + { + case QVariant::DateTime: + qDebug() << "DateTime"; + v[index + i] = QVariant( qMakeDate( fld.data ) ); + break; + case QVariant::Double: + case QVariant::Int: + case QVariant::LongLong: + if ( d->q->numericalPrecisionPolicy() != QSql::HighPrecision ) + { + if (( d->q->numericalPrecisionPolicy() == QSql::LowPrecisionDouble ) + && ( fld.typ == QVariant::Double ) ) + { + v[index + i] = *reinterpret_cast( fld.data ); + qDebug() << "double" << v[index + i].toDouble(); + break; + } + else if (( d->q->numericalPrecisionPolicy() == QSql::LowPrecisionInt64 ) + && ( fld.typ == QVariant::LongLong ) ) + { + qint64 qll = 0; + int r = OCINumberToInt( d->err, reinterpret_cast( fld.data ), sizeof( qint64 ), + OCI_NUMBER_SIGNED, &qll ); + if ( r == OCI_SUCCESS ) + { + v[index + i] = qll; + qDebug() << "qint64" << qll; + } + else + { + qDebug() << "qint64 invalid"; + v[index + i] = QVariant(); + } + break; + } + else if (( d->q->numericalPrecisionPolicy() == QSql::LowPrecisionInt32 ) + && ( fld.typ == QVariant::Int ) ) + { + v[index + i] = *reinterpret_cast( fld.data ); + qDebug() << "int" << v[index + i].toInt(); + break; + } + } + // else fall through + case QVariant::String: + qDebug() << "String"; + v[index + i] = QString( reinterpret_cast( fld.data ) ); + qDebug() << "string" << v[index + i].toString(); + break; + case QVariant::ByteArray: + if ( fld.oraType == SQLT_NTY && fld.oraTypeName == "SDO_GEOMETRY" ) + { + qDebug() << "SQLT_NTY SOD_GEOMETRY"; + convertToWkb( v[ index+i ] ); + } + else + { + qDebug() << "ByteArray length =" << fld.len; + if ( fld.len > 0 ) + v[index + i] = QByteArray( fld.data, fld.len ); + else + v[index + i] = QVariant( QVariant::ByteArray ); + } + break; + default: + qWarning( "QOCISpatialCols::value: unknown data type %s", QVariant::typeToName( fld.typ ) ); + break; + } + } +} + +QOCISpatialResultPrivate::QOCISpatialResultPrivate( QOCISpatialResult *result, const QOCISpatialDriverPrivate *driver ) + : cols( 0 ) + , q( result ) + , env( driver->env ) + , err( 0 ) + , svc( const_cast( driver->svc ) ) + , sql( 0 ) + , transaction( driver->transaction ) + , serverVersion( driver->serverVersion ) + , prefetchRows( driver->prefetchRows ) + , prefetchMem( driver->prefetchMem ) + , geometryTDO( driver->geometryTDO ) + , geometryObj( 0 ) + , geometryInd( 0 ) +{ + ENTER + int r = OCIHandleAlloc( env, + reinterpret_cast( &err ), + OCI_HTYPE_ERROR, + 0, + 0 ); + if ( r != OCI_SUCCESS ) + qWarning( "QOCISpatialResult: unable to alloc error handle" ); +} + +QOCISpatialResultPrivate::~QOCISpatialResultPrivate() +{ + ENTER + delete cols; + + int r; + if ( geometryObj ) + { + r = OCIObjectFree( env, err, geometryObj, OCI_OBJECTFREE_FORCE ); + if ( r != OCI_SUCCESS ) + qOraWarning( "~QOCISpatialResult: unable to free geometry object", err ); + } + + r = OCIHandleFree( err, OCI_HTYPE_ERROR ); + if ( r != OCI_SUCCESS ) + qWarning( "~QOCISpatialResult: unable to free statement handle" ); +} + + +//////////////////////////////////////////////////////////////////////////// + +QOCISpatialResult::QOCISpatialResult( const QOCISpatialDriver * db, const QOCISpatialDriverPrivate* p ) + : QSqlCachedResult( db ) +{ + ENTER + d = new QOCISpatialResultPrivate( this, p ); +} + +QOCISpatialResult::~QOCISpatialResult() +{ + ENTER + if ( d->sql ) + { + int r = OCIHandleFree( d->sql, OCI_HTYPE_STMT ); + if ( r != OCI_SUCCESS ) + qWarning( "~QOCISpatialResult: unable to free statement handle" ); + } + delete d; +} + +QVariant QOCISpatialResult::handle() const +{ + ENTER + return QVariant::fromValue( d->sql ); +} + +bool QOCISpatialResult::reset( const QString& query ) +{ + ENTER + if ( !prepare( query ) ) + return false; + return exec(); +} + +bool QOCISpatialResult::gotoNext( QSqlCachedResult::ValueCache &values, int index ) +{ + ENTER + qDebug() << "gotoNext( index =" << index << ")"; + if ( at() == QSql::AfterLastRow ) + return false; + + bool piecewise = false; + int r = OCI_SUCCESS; + r = OCIStmtFetch2( d->sql, d->err, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT ); + + if ( index < 0 ) //not interested in values + return r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO; + + switch ( r ) + { + case OCI_SUCCESS: + break; + case OCI_SUCCESS_WITH_INFO: + qOraWarning( "SuccessWithInfo: ", d->err ); + r = OCI_SUCCESS; //ignore it + break; + case OCI_NO_DATA: + // end of rowset + return false; + case OCI_NEED_DATA: + qDebug( "NEED DATA - fetching piecewise" ); + piecewise = true; + r = OCI_SUCCESS; + break; + case OCI_ERROR: + if ( qOraErrorNumber( d->err ) == 1406 ) + { + qWarning( "QOCISpatial Warning: data truncated for %s", lastQuery().toLocal8Bit().constData() ); + r = OCI_SUCCESS; /* ignore it */ + break; + } + // fall through + default: + qOraWarning( "goto next error: ", d->err ); + setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialResult", + "Unable to goto next" ), + QSqlError::StatementError, d->err ) ); + break; + } + + // need to read piecewise before assigning values + if ( r == OCI_SUCCESS && piecewise ) + r = d->cols->readPiecewise( values, index ); + + if ( r == OCI_SUCCESS ) + d->cols->getValues( values, index ); + + if ( r == OCI_SUCCESS ) + r = d->cols->readLOBs( values, index ); + + if ( r != OCI_SUCCESS ) + setAt( QSql::AfterLastRow ); + + return r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO; +} + +int QOCISpatialResult::size() +{ + ENTER + int rowCount; + if ( OCIAttrGet( d->sql, + OCI_HTYPE_STMT, + &rowCount, + NULL, + OCI_ATTR_ROWS_FETCHED, + d->err ) == OCI_SUCCESS ) + { + return rowCount; + } + else + { + return -1; + } +} + +int QOCISpatialResult::numRowsAffected() +{ + ENTER + int rowCount; + OCIAttrGet( d->sql, + OCI_HTYPE_STMT, + &rowCount, + NULL, + OCI_ATTR_ROW_COUNT, + d->err ); + return rowCount; +} + +bool QOCISpatialResult::prepare( const QString& query ) +{ + ENTER + qDebug() << "prepare(" << query << ")"; + + int r = 0; + QSqlResult::prepare( query ); + + delete d->cols; + d->cols = 0; + QSqlCachedResult::cleanup(); + + if ( d->sql ) + { + r = OCIHandleFree( d->sql, OCI_HTYPE_STMT ); + if ( r != OCI_SUCCESS ) + qOraWarning( "unable to free statement handle:", d->err ); + } + if ( query.isEmpty() ) + return false; + r = OCIHandleAlloc( d->env, + reinterpret_cast( &d->sql ), + OCI_HTYPE_STMT, + 0, + 0 ); + if ( r != OCI_SUCCESS ) + { + qOraWarning( "unable to alloc statement:", d->err ); + setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialResult", + "Unable to alloc statement" ), QSqlError::StatementError, d->err ) ); + return false; + } + d->setStatementAttributes(); + const OraText *txt = reinterpret_cast( query.utf16() ); + const int len = query.length() * sizeof( QChar ); + r = OCIStmtPrepare( d->sql, + d->err, + txt, + len, + OCI_NTV_SYNTAX, + OCI_DEFAULT ); + if ( r != OCI_SUCCESS ) + { + qOraWarning( "unable to prepare statement:", d->err ); + setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialResult", + "Unable to prepare statement" ), QSqlError::StatementError, d->err ) ); + return false; + } + + return true; +} + +bool QOCISpatialResult::exec() +{ + ENTER + int r = 0; + ub2 stmtType = 0; + ub4 iters; + ub4 mode; + QList tmpStorage; + IndicatorArray indicators( boundValueCount() ); + SizeArray tmpSizes( boundValueCount() ); + + r = OCIAttrGet( d->sql, + OCI_HTYPE_STMT, + &stmtType, + NULL, + OCI_ATTR_STMT_TYPE, + d->err ); + + if ( r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO ) + { + qOraWarning( "Unable to get statement type:", d->err ); + setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialResult", + "Unable to get statement type" ), QSqlError::StatementError, d->err ) ); +#ifdef QOCISPATIAL_DEBUG + qDebug() << "lastQuery()" << lastQuery(); +#endif + return false; + } + + if ( stmtType == OCI_STMT_SELECT ) + { + iters = 0; + mode = OCI_DEFAULT; + } + else + { + iters = 1; + mode = d->transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS; + } + + qDebug() << "iters:" << iters; + + // bind placeholders + if ( boundValueCount() > 0 + && d->bindValues( boundValues(), indicators, tmpSizes, tmpStorage ) != OCI_SUCCESS ) + { + qOraWarning( "unable to bind value: ", d->err ); + setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialResult", "Unable to bind value" ), + QSqlError::StatementError, d->err ) ); +#ifdef QOCISPATIAL_DEBUG + qDebug() << "lastQuery()" << lastQuery(); +#endif + return false; + } + + // execute + r = OCIStmtExecute( d->svc, d->sql, d->err, iters, 0, 0, 0, mode ); + if ( r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO ) + { + qOraWarning( "unable to execute statement:", d->err ); + setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialResult", + "Unable to execute statement" ), QSqlError::StatementError, d->err ) ); +#ifdef QOCISPATIAL_DEBUG + qDebug() << "lastQuery()" << lastQuery(); +#endif + return false; + } + + if ( stmtType == OCI_STMT_SELECT ) + { + ub4 parmCount = 0; + int r = OCIAttrGet( d->sql, OCI_HTYPE_STMT, reinterpret_cast( &parmCount ), + 0, OCI_ATTR_PARAM_COUNT, d->err ); + if ( r == OCI_SUCCESS && !d->cols ) + { + d->sdoobj = 0; + d->sdoind = 0; + d->cols = new QOCISpatialCols( parmCount, d ); + } + else + qOraWarning( "get param count failed:", d->err ); + setSelect( true ); + QSqlCachedResult::init( parmCount ); + } + else /* non-SELECT */ + { + setSelect( false ); + } + setAt( QSql::BeforeFirstRow ); + setActive( true ); + + if ( hasOutValues() ) + d->outValues( boundValues(), indicators, tmpStorage ); + + return true; +} + +QSqlRecord QOCISpatialResult::record() const +{ + ENTER + QSqlRecord inf; + if ( !isActive() || !isSelect() || !d->cols ) + return inf; + return d->cols->rec; +} + +QVariant QOCISpatialResult::lastInsertId() const +{ + ENTER + if ( isActive() ) + { + QOCISpatialRowIdPointer ptr( new QOCISpatialRowId( d->env ) ); + + int r = OCIAttrGet( d->sql, OCI_HTYPE_STMT, ptr.constData()->id, + 0, OCI_ATTR_ROWID, d->err ); + if ( r == OCI_SUCCESS ) + return QVariant::fromValue( ptr ); + } + return QVariant(); +} + +void QOCISpatialResult::virtual_hook( int id, void *data ) +{ + ENTER + Q_ASSERT( data ); + + switch ( id ) + { + case QSqlResult::BatchOperation: + QOCISpatialCols::execBatch( d, boundValues(), *reinterpret_cast( data ) ); + break; + default: + QSqlCachedResult::virtual_hook( id, data ); + } +} + +//////////////////////////////////////////////////////////////////////////// + + +QOCISpatialDriver::QOCISpatialDriver( QObject* parent ) + : QSqlDriver( parent ) +{ + ENTER + d = new QOCISpatialDriverPrivate(); + +#ifdef QOCISPATIAL_THREADED + const ub4 mode = OCI_UTF16 | OCI_OBJECT | OCI_THREADED; +#else + const ub4 mode = OCI_UTF16 | OCI_OBJECT; +#endif + int r = OCIEnvCreate( &d->env, + mode, + NULL, + NULL, + NULL, + NULL, + 0, + NULL ); + if ( r != OCI_SUCCESS ) + { + qWarning( "QOCISpatialDriver: unable to create environment" ); + setLastError( qMakeError( tr( "Unable to initialize", "QOCISpatialDriver" ), + QSqlError::ConnectionError, d->err ) ); + return; + } + + d->allocErrorHandle(); +} + +QOCISpatialDriver::QOCISpatialDriver( OCIEnv* env, OCISvcCtx* ctx, QObject* parent ) + : QSqlDriver( parent ) +{ + ENTER + d = new QOCISpatialDriverPrivate(); + d->env = env; + d->svc = ctx; + + d->allocErrorHandle(); + + if ( env && ctx ) + { + setOpen( true ); + setOpenError( false ); + } +} + +QOCISpatialDriver::~QOCISpatialDriver() +{ + ENTER + if ( isOpen() ) + close(); + int r = OCIHandleFree( d->err, OCI_HTYPE_ERROR ); + if ( r != OCI_SUCCESS ) + qWarning( "Unable to free Error handle: %d", r ); + r = OCIHandleFree( d->env, OCI_HTYPE_ENV ); + if ( r != OCI_SUCCESS ) + qWarning( "Unable to free Environment handle: %d", r ); + + delete d; +} + +bool QOCISpatialDriver::hasFeature( DriverFeature f ) const +{ + ENTER + switch ( f ) + { + case Transactions: + case LastInsertId: + case BLOB: + case PreparedQueries: + case NamedPlaceholders: + case BatchOperations: + case LowPrecisionNumbers: + return true; + case QuerySize: + case PositionalPlaceholders: + case SimpleLocking: + case EventNotifications: + case FinishQuery: + case MultipleResultSets: + return false; + case Unicode: + return d->serverVersion >= 9; + } + return false; +} + +static void qParseOpts( const QString &options, QOCISpatialDriverPrivate *d ) +{ + ENTER + const QStringList opts( options.split( QLatin1Char( ';' ), QString::SkipEmptyParts ) ); + for ( int i = 0; i < opts.count(); ++i ) + { + const QString tmp( opts.at( i ) ); + int idx; + if (( idx = tmp.indexOf( QLatin1Char( '=' ) ) ) == -1 ) + { + qWarning( "QOCISpatialDriver::parseArgs: Invalid parameter: '%s'", + tmp.toLocal8Bit().constData() ); + continue; + } + const QString opt = tmp.left( idx ); + const QString val = tmp.mid( idx + 1 ).simplified(); + bool ok; + if ( opt == QLatin1String( "OCI_ATTR_PREFETCH_ROWS" ) ) + { + d->prefetchRows = val.toInt( &ok ); + if ( !ok ) + d->prefetchRows = -1; + } + else if ( opt == QLatin1String( "OCI_ATTR_PREFETCH_MEMORY" ) ) + { + d->prefetchMem = val.toInt( &ok ); + if ( !ok ) + d->prefetchMem = -1; + } + else + { + qWarning( "QOCISpatialDriver::parseArgs: Invalid parameter: '%s'", + opt.toLocal8Bit().constData() ); + } + } +} + +bool QOCISpatialDriver::open( const QString & db, + const QString & user, + const QString & password, + const QString & hostname, + int port, + const QString &opts ) +{ + ENTER + int r; + + if ( isOpen() ) + close(); + + qParseOpts( opts, d ); + + // Connect without tnsnames.ora if a hostname is given + QString connectionString = db; + if ( !hostname.isEmpty() ) + connectionString = + QString::fromLatin1( "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(Host=%1)(Port=%2))" + "(CONNECT_DATA=(SID=%3)))" ).arg( hostname ).arg(( port > -1 ? port : 1521 ) ).arg( db ); + + r = OCIHandleAlloc( d->env, reinterpret_cast( &d->srvhp ), OCI_HTYPE_SERVER, 0, 0 ); + if ( r == OCI_SUCCESS ) + r = OCIServerAttach( d->srvhp, d->err, reinterpret_cast( connectionString.utf16() ), + connectionString.length() * sizeof( QChar ), OCI_DEFAULT ); + if ( r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO ) + r = OCIHandleAlloc( d->env, reinterpret_cast( &d->svc ), OCI_HTYPE_SVCCTX, 0, 0 ); + if ( r == OCI_SUCCESS ) + r = OCIAttrSet( d->svc, OCI_HTYPE_SVCCTX, d->srvhp, 0, OCI_ATTR_SERVER, d->err ); + if ( r == OCI_SUCCESS ) + r = OCIHandleAlloc( d->env, reinterpret_cast( &d->authp ), OCI_HTYPE_SESSION, 0, 0 ); + if ( r == OCI_SUCCESS ) + r = OCIAttrSet( d->authp, OCI_HTYPE_SESSION, const_cast( user.utf16() ), + user.length() * sizeof( QChar ), OCI_ATTR_USERNAME, d->err ); + if ( r == OCI_SUCCESS ) + r = OCIAttrSet( d->authp, OCI_HTYPE_SESSION, const_cast( password.utf16() ), + password.length() * sizeof( QChar ), OCI_ATTR_PASSWORD, d->err ); + + OCITrans* trans; + if ( r == OCI_SUCCESS ) + r = OCIHandleAlloc( d->env, reinterpret_cast( &trans ), OCI_HTYPE_TRANS, 0, 0 ); + if ( r == OCI_SUCCESS ) + r = OCIAttrSet( d->svc, OCI_HTYPE_SVCCTX, trans, 0, OCI_ATTR_TRANS, d->err ); + + if ( r == OCI_SUCCESS ) + { + if ( user.isEmpty() && password.isEmpty() ) + r = OCISessionBegin( d->svc, d->err, d->authp, OCI_CRED_EXT, OCI_DEFAULT ); + else + r = OCISessionBegin( d->svc, d->err, d->authp, OCI_CRED_RDBMS, OCI_DEFAULT ); + } + if ( r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO ) + r = OCIAttrSet( d->svc, OCI_HTYPE_SVCCTX, d->authp, 0, OCI_ATTR_SESSION, d->err ); + + if ( r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO ) + { + d->geometryTDO = d->tdo( "MDSYS.SDO_GEOMETRY" ); + if ( !d->geometryTDO ) + { + qDebug() << "MDSYS.SDO_GEOMETRY TDO not found"; + r = OCI_INVALID_HANDLE; + } + } + + if ( r != OCI_SUCCESS ) + { + setLastError( qMakeError( tr( "Unable to logon" ), QSqlError::ConnectionError, d->err ) ); + setOpenError( true ); + if ( d->authp ) + OCIHandleFree( d->authp, OCI_HTYPE_SESSION ); + d->authp = 0; + if ( d->srvhp ) + OCIHandleFree( d->srvhp, OCI_HTYPE_SERVER ); + d->srvhp = 0; + return false; + } + + // get server version + char vertxt[512]; + r = OCIServerVersion( d->svc, + d->err, + reinterpret_cast( vertxt ), + sizeof( vertxt ), + OCI_HTYPE_SVCCTX ); + if ( r != OCI_SUCCESS ) + { + qWarning( "QOCISpatialDriver::open: could not get Oracle server version." ); + } + else + { + QString versionStr; + versionStr = QString( reinterpret_cast( vertxt ) ); + QRegExp vers( QLatin1String( "([0-9]+)\\.[0-9\\.]+[0-9]" ) ); + if ( vers.indexIn( versionStr ) >= 0 ) + d->serverVersion = vers.cap( 1 ).toInt(); + if ( d->serverVersion == 0 ) + d->serverVersion = -1; + } + + setOpen( true ); + setOpenError( false ); + d->user = user; + + return true; +} + +void QOCISpatialDriver::close() +{ + ENTER + if ( !isOpen() ) + return; + + OCISessionEnd( d->svc, d->err, d->authp, OCI_DEFAULT ); + OCIServerDetach( d->srvhp, d->err, OCI_DEFAULT ); + OCIHandleFree( d->authp, OCI_HTYPE_SESSION ); + d->authp = 0; + OCIHandleFree( d->srvhp, OCI_HTYPE_SERVER ); + d->srvhp = 0; + OCIHandleFree( d->svc, OCI_HTYPE_SVCCTX ); + d->svc = 0; + setOpen( false ); + setOpenError( false ); +} + +QSqlResult *QOCISpatialDriver::createResult() const +{ + ENTER + return new QOCISpatialResult( this, d ); +} + +bool QOCISpatialDriver::beginTransaction() +{ + ENTER + if ( !isOpen() ) + { + qWarning( "QOCISpatialDriver::beginTransaction: Database not open" ); + return false; + } + int r = OCITransStart( d->svc, + d->err, + 2, + OCI_TRANS_READWRITE ); + if ( r == OCI_ERROR ) + { + qOraWarning( "Unable to begin transaction: ", d->err ); + setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialDriver", + "Unable to begin transaction" ), QSqlError::TransactionError, d->err ) ); + return false; + } + d->transaction = true; + return true; +} + +bool QOCISpatialDriver::commitTransaction() +{ + ENTER + if ( !isOpen() ) + { + qWarning( "QOCISpatialDriver::commitTransaction: Database not open" ); + return false; + } + int r = OCITransCommit( d->svc, + d->err, + 0 ); + if ( r == OCI_ERROR ) + { + qOraWarning( "Unable to commit transaction:", d->err ); + setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialDriver", + "Unable to commit transaction" ), QSqlError::TransactionError, d->err ) ); + return false; + } + d->transaction = false; + return true; +} + +bool QOCISpatialDriver::rollbackTransaction() +{ + ENTER + if ( !isOpen() ) + { + qWarning( "QOCISpatialDriver::rollbackTransaction: Database not open" ); + return false; + } + int r = OCITransRollback( d->svc, + d->err, + 0 ); + if ( r == OCI_ERROR ) + { + qOraWarning( "QOCISpatialDriver::rollbackTransaction:", d->err ); + setLastError( qMakeError( QCoreApplication::translate( "QOCISpatialDriver", + "Unable to rollback transaction" ), QSqlError::TransactionError, d->err ) ); + return false; + } + d->transaction = false; + return true; +} + +QStringList QOCISpatialDriver::tables( QSql::TableType type ) const +{ + ENTER + QStringList tl; + QStringList sysUsers = QStringList() << QLatin1String( "MDSYS" ) + << QLatin1String( "LBACSYS" ) + << QLatin1String( "SYS" ) + << QLatin1String( "SYSTEM" ) + << QLatin1String( "WKSYS" ) + << QLatin1String( "CTXSYS" ) + << QLatin1String( "WMSYS" ); + + QString user = d->user; + if ( isIdentifierEscaped( user, QSqlDriver::TableName ) ) + user = stripDelimiters( user, QSqlDriver::TableName ); + else + user = user.toUpper(); + + if ( sysUsers.contains( user ) ) + sysUsers.removeAll( user );; + + if ( !isOpen() ) + return tl; + + QSqlQuery t( createResult() ); + t.setForwardOnly( true ); + if ( type & QSql::Tables ) + { + QString query = QLatin1String( "select owner, table_name from all_tables where " ); + QStringList whereList; + foreach ( const QString &sysUserName, sysUsers ) + whereList << QLatin1String( "owner != '" ) + sysUserName + QLatin1String( "' " ); + t.exec( query + whereList.join( QLatin1String( " and " ) ) ); + + while ( t.next() ) + { + if ( t.value( 0 ).toString().toUpper() != user.toUpper() ) + tl.append( t.value( 0 ).toString() + QLatin1Char( '.' ) + t.value( 1 ).toString() ); + else + tl.append( t.value( 1 ).toString() ); + } + + // list all table synonyms as well + query = QLatin1String( "select owner, synonym_name from all_synonyms where " ); + t.exec( query + whereList.join( QLatin1String( " and " ) ) ); + while ( t.next() ) + { + if ( t.value( 0 ).toString() != d->user ) + tl.append( t.value( 0 ).toString() + QLatin1Char( '.' ) + t.value( 1 ).toString() ); + else + tl.append( t.value( 1 ).toString() ); + } + } + if ( type & QSql::Views ) + { + QString query = QLatin1String( "select owner, view_name from all_views where " ); + QStringList whereList; + foreach ( const QString &sysUserName, sysUsers ) + whereList << QLatin1String( "owner != '" ) + sysUserName + QLatin1String( "' " ); + t.exec( query + whereList.join( QLatin1String( " and " ) ) ); + while ( t.next() ) + { + if ( t.value( 0 ).toString().toUpper() != d->user.toUpper() ) + tl.append( t.value( 0 ).toString() + QLatin1Char( '.' ) + t.value( 1 ).toString() ); + else + tl.append( t.value( 1 ).toString() ); + } + } + if ( type & QSql::SystemTables ) + { + t.exec( QLatin1String( "select table_name from dictionary" ) ); + while ( t.next() ) + { + tl.append( t.value( 0 ).toString() ); + } + QString query = QLatin1String( "select owner, table_name from all_tables where " ); + QStringList whereList; + foreach ( const QString &sysUserName, sysUsers ) + whereList << QLatin1String( "owner = '" ) + sysUserName + QLatin1String( "' " ); + t.exec( query + whereList.join( QLatin1String( " or " ) ) ); + + while ( t.next() ) + { + if ( t.value( 0 ).toString().toUpper() != user.toUpper() ) + tl.append( t.value( 0 ).toString() + QLatin1Char( '.' ) + t.value( 1 ).toString() ); + else + tl.append( t.value( 1 ).toString() ); + } + + // list all table synonyms as well + query = QLatin1String( "select owner, synonym_name from all_synonyms where " ); + t.exec( query + whereList.join( QLatin1String( " or " ) ) ); + while ( t.next() ) + { + if ( t.value( 0 ).toString() != d->user ) + tl.append( t.value( 0 ).toString() + QLatin1String( "." ) + t.value( 1 ).toString() ); + else + tl.append( t.value( 1 ).toString() ); + } + } + return tl; +} + +void qSplitTableAndOwner( const QString & tname, QString * tbl, + QString * owner ) +{ + ENTER + int i = tname.indexOf( QLatin1Char( '.' ) ); // prefixed with owner? + if ( i != -1 ) + { + *tbl = tname.right( tname.length() - i - 1 ); + *owner = tname.left( i ); + } + else + { + *tbl = tname; + } +} + +QSqlRecord QOCISpatialDriver::record( const QString& tablename ) const +{ + ENTER + QSqlRecord fil; + if ( !isOpen() ) + return fil; + + QSqlQuery t( createResult() ); + // using two separate queries for this is A LOT faster than using + // eg. a sub-query on the sys.synonyms table + QString stmt( QLatin1String( "select column_name, data_type, data_length, " + "data_precision, data_scale, nullable, data_default%1" + "from all_tab_columns a " + "where a.table_name=%2" ) ); + if ( d->serverVersion >= 9 ) + stmt = stmt.arg( QLatin1String( ", char_length " ) ); + else + stmt = stmt.arg( QLatin1String( " " ) ); + bool buildRecordInfo = false; + QString table, owner, tmpStmt; + qSplitTableAndOwner( tablename, &table, &owner ); + + if ( isIdentifierEscaped( table, QSqlDriver::TableName ) ) + table = stripDelimiters( table, QSqlDriver::TableName ); + else + table = table.toUpper(); + + tmpStmt = stmt.arg( QLatin1Char( '\'' ) + table + QLatin1Char( '\'' ) ); + if ( owner.isEmpty() ) + { + owner = d->user; + } + + if ( isIdentifierEscaped( owner, QSqlDriver::TableName ) ) + owner = stripDelimiters( owner, QSqlDriver::TableName ); + else + owner = owner.toUpper(); + + tmpStmt += QLatin1String( " and a.owner='" ) + owner + QLatin1Char( '\'' ); + t.setForwardOnly( true ); + t.exec( tmpStmt ); + if ( !t.next() ) // try and see if the tablename is a synonym + { + stmt = stmt + QLatin1String( " join all_synonyms b " + "on a.owner=b.table_owner and a.table_name=b.table_name " + "where b.owner='" ) + owner + + QLatin1String( "' and b.synonym_name='" ) + table + + QLatin1Char( '\'' ); + t.setForwardOnly( true ); + t.exec( stmt ); + if ( t.next() ) + buildRecordInfo = true; + } + else + { + buildRecordInfo = true; + } + QStringList keywords = QStringList() << + QLatin1String( "NUMBER" ) << QLatin1String( "FLOAT" ) << QLatin1String( "BINARY_FLOAT" ) << QLatin1String( "BINARY_DOUBLE" ); + if ( buildRecordInfo ) + { + do + { + QVariant::Type ty = qDecodeOCIType( t.value( 1 ).toString(), t.numericalPrecisionPolicy() ); + QSqlField f( t.value( 0 ).toString(), ty ); + f.setRequired( t.value( 5 ).toString() == QLatin1String( "N" ) ); + f.setPrecision( t.value( 4 ).toInt() ); + if ( d->serverVersion >= 9 && ( ty == QVariant::String ) && !t.isNull( 3 ) && !keywords.contains( t.value( 1 ).toString() ) ) + { + // Oracle9: data_length == size in bytes, char_length == amount of characters + f.setLength( t.value( 7 ).toInt() ); + } + else + { + f.setLength( t.value( t.isNull( 3 ) ? 2 : 3 ).toInt() ); + } + f.setDefaultValue( t.value( 6 ) ); + fil.append( f ); + } + while ( t.next() ); + } + return fil; +} + +QSqlIndex QOCISpatialDriver::primaryIndex( const QString& tablename ) const +{ + QSqlIndex idx( tablename ); + if ( !isOpen() ) + return idx; + QSqlQuery t( createResult() ); + QString stmt( QLatin1String( "select b.column_name, b.index_name, a.table_name, a.owner " + "from all_constraints a, all_ind_columns b " + "where a.constraint_type='P' " + "and b.index_name = a.constraint_name " + "and b.index_owner = a.owner" ) ); + + bool buildIndex = false; + QString table, owner, tmpStmt; + qSplitTableAndOwner( tablename, &table, &owner ); + + if ( isIdentifierEscaped( table, QSqlDriver::TableName ) ) + table = stripDelimiters( table, QSqlDriver::TableName ); + else + table = table.toUpper(); + + tmpStmt = stmt + QLatin1String( " and a.table_name='" ) + table + QLatin1Char( '\'' ); + if ( owner.isEmpty() ) + { + owner = d->user; + } + + if ( isIdentifierEscaped( owner, QSqlDriver::TableName ) ) + owner = stripDelimiters( owner, QSqlDriver::TableName ); + else + owner = owner.toUpper(); + + tmpStmt += QLatin1String( " and a.owner='" ) + owner + QLatin1Char( '\'' ); + t.setForwardOnly( true ); + t.exec( tmpStmt ); + + if ( !t.next() ) + { + stmt += QLatin1String( " and a.table_name=(select tname from sys.synonyms " + "where sname='" ) + table + QLatin1String( "' and creator=a.owner)" ); + t.setForwardOnly( true ); + t.exec( stmt ); + if ( t.next() ) + { + owner = t.value( 3 ).toString(); + buildIndex = true; + } + } + else + { + buildIndex = true; + } + if ( buildIndex ) + { + QSqlQuery tt( createResult() ); + tt.setForwardOnly( true ); + idx.setName( t.value( 1 ).toString() ); + do + { + tt.exec( QLatin1String( "select data_type from all_tab_columns where table_name='" ) + + t.value( 2 ).toString() + QLatin1String( "' and column_name='" ) + + t.value( 0 ).toString() + QLatin1String( "' and owner='" ) + + owner + QLatin1Char( '\'' ) ); + if ( !tt.next() ) + { + return QSqlIndex(); + } + QSqlField f( t.value( 0 ).toString(), qDecodeOCIType( tt.value( 0 ).toString(), t.numericalPrecisionPolicy() ) ); + idx.append( f ); + } + while ( t.next() ); + return idx; + } + return QSqlIndex(); +} + +QString QOCISpatialDriver::formatValue( const QSqlField &field, bool trimStrings ) const +{ + ENTER + switch ( field.type() ) + { + case QVariant::DateTime: + { + QDateTime datetime = field.value().toDateTime(); + QString datestring; + if ( datetime.isValid() ) + { + datestring = QLatin1String( "TO_DATE('" ) + QString::number( datetime.date().year() ) + + QLatin1Char( '-' ) + + QString::number( datetime.date().month() ) + QLatin1Char( '-' ) + + QString::number( datetime.date().day() ) + QLatin1Char( ' ' ) + + QString::number( datetime.time().hour() ) + QLatin1Char( ':' ) + + QString::number( datetime.time().minute() ) + QLatin1Char( ':' ) + + QString::number( datetime.time().second() ) + + QLatin1String( "','YYYY-MM-DD HH24:MI:SS')" ); + } + else + { + datestring = QLatin1String( "NULL" ); + } + return datestring; + } + case QVariant::Time: + { + QDateTime datetime = field.value().toDateTime(); + QString datestring; + if ( datetime.isValid() ) + { + datestring = QLatin1String( "TO_DATE('" ) + + QString::number( datetime.time().hour() ) + QLatin1Char( ':' ) + + QString::number( datetime.time().minute() ) + QLatin1Char( ':' ) + + QString::number( datetime.time().second() ) + + QLatin1String( "','HH24:MI:SS')" ); + } + else + { + datestring = QLatin1String( "NULL" ); + } + return datestring; + } + case QVariant::Date: + { + QDate date = field.value().toDate(); + QString datestring; + if ( date.isValid() ) + { + datestring = QLatin1String( "TO_DATE('" ) + QString::number( date.year() ) + + QLatin1Char( '-' ) + + QString::number( date.month() ) + QLatin1Char( '-' ) + + QString::number( date.day() ) + QLatin1String( "','YYYY-MM-DD')" ); + } + else + { + datestring = QLatin1String( "NULL" ); + } + return datestring; + } + default: + break; + } + return QSqlDriver::formatValue( field, trimStrings ); +} + +QVariant QOCISpatialDriver::handle() const +{ + ENTER + return QVariant::fromValue( d->env ); +} + +QString QOCISpatialDriver::escapeIdentifier( const QString &identifier, IdentifierType type ) const +{ + ENTER + QString res = identifier; + if ( !identifier.isEmpty() && !isIdentifierEscaped( identifier, type ) ) + { + res.replace( QLatin1Char( '"' ), QLatin1String( "\"\"" ) ); + res.prepend( QLatin1Char( '"' ) ).append( QLatin1Char( '"' ) ); + res.replace( QLatin1Char( '.' ), QLatin1String( "\".\"" ) ); + } + return res; +} + +QT_END_NAMESPACE + +// vim: set sw=4 expandtab : diff --git a/src/providers/oracle/ocispatial/qsql_ocispatial.h b/src/providers/oracle/ocispatial/qsql_ocispatial.h new file mode 100644 index 000000000000..743882804def --- /dev/null +++ b/src/providers/oracle/ocispatial/qsql_ocispatial.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQL_OCISPATIAL_H +#define QSQL_OCISPATIAL_H + +#include +#include +#include "qsqlcachedresult_p.h" + +#ifdef QT_PLUGIN +#define Q_EXPORT_SQLDRIVER_OCISPATIAL +#else +#define Q_EXPORT_SQLDRIVER_OCISPATIAL Q_SQL_EXPORT +#endif + +QT_BEGIN_HEADER + +typedef struct OCIEnv OCIEnv; +typedef struct OCISvcCtx OCISvcCtx; + +QT_BEGIN_NAMESPACE + +class QOCISpatialDriver; +class QOCISpatialCols; +struct QOCISpatialDriverPrivate; +struct QOCISpatialResultPrivate; + +class Q_EXPORT_SQLDRIVER_OCISPATIAL QOCISpatialResult : public QSqlCachedResult +{ + friend class QOCISpatialDriver; + friend struct QOCISpatialResultPrivate; + friend class QOCISpatialCols; + public: + QOCISpatialResult( const QOCISpatialDriver * db, const QOCISpatialDriverPrivate* p ); + ~QOCISpatialResult(); + bool prepare( const QString& query ); + bool exec(); + QVariant handle() const; + + protected: + bool gotoNext( ValueCache &values, int index ); + bool reset( const QString& query ); + int size(); + int numRowsAffected(); + QSqlRecord record() const; + QVariant lastInsertId() const; + void virtual_hook( int id, void *data ); + + private: + QOCISpatialResultPrivate *d; +}; + +class Q_EXPORT_SQLDRIVER_OCISPATIAL QOCISpatialDriver : public QSqlDriver +{ + Q_OBJECT + friend struct QOCISpatialResultPrivate; + friend class QOCISpatialPrivate; + public: + explicit QOCISpatialDriver( QObject* parent = 0 ); + QOCISpatialDriver( OCIEnv* env, OCISvcCtx* ctx, QObject* parent = 0 ); + ~QOCISpatialDriver(); + bool hasFeature( DriverFeature f ) const; + bool open( const QString & db, + const QString & user, + const QString & password, + const QString & host, + int port, + const QString& connOpts ); + void close(); + QSqlResult *createResult() const; + QStringList tables( QSql::TableType ) const; + QSqlRecord record( const QString& tablename ) const; + QSqlIndex primaryIndex( const QString& tablename ) const; + QString formatValue( const QSqlField &field, + bool trimStrings ) const; + QVariant handle() const; + QString escapeIdentifier( const QString &identifier, IdentifierType ) const; + + protected: + bool beginTransaction(); + bool commitTransaction(); + bool rollbackTransaction(); + private: + QOCISpatialDriverPrivate *d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSQL_OCISPATIAL_H diff --git a/src/providers/oracle/ocispatial/qsqlcachedresult_p.h b/src/providers/oracle/ocispatial/qsqlcachedresult_p.h new file mode 100644 index 000000000000..2286e2c524c8 --- /dev/null +++ b/src/providers/oracle/ocispatial/qsqlcachedresult_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtSql module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSQLCACHEDRESULT_P_H +#define QSQLCACHEDRESULT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "QtSql/qsqlresult.h" + +QT_BEGIN_NAMESPACE + +class QVariant; +template class QVector; + +class QSqlCachedResultPrivate; + +class Q_SQL_EXPORT QSqlCachedResult: public QSqlResult +{ + public: + virtual ~QSqlCachedResult(); + + typedef QVector ValueCache; + + protected: + QSqlCachedResult( const QSqlDriver * db ); + + void init( int colCount ); + void cleanup(); + void clearValues(); + + virtual bool gotoNext( ValueCache &values, int index ) = 0; + + QVariant data( int i ); + bool isNull( int i ); + bool fetch( int i ); + bool fetchNext(); + bool fetchPrevious(); + bool fetchFirst(); + bool fetchLast(); + + int colCount() const; + ValueCache &cache(); + + void virtual_hook( int id, void *data ); + private: + bool cacheNext(); + QSqlCachedResultPrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QSQLCACHEDRESULT_P_H diff --git a/src/providers/oracle/ocispatial/wkbptr.h b/src/providers/oracle/ocispatial/wkbptr.h new file mode 100644 index 000000000000..ab5e929e946e --- /dev/null +++ b/src/providers/oracle/ocispatial/wkbptr.h @@ -0,0 +1,66 @@ +/*************************************************************************** + wkbptr.h + --------------------- + begin : Dezember 2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + *************************************************************************** + * * + * This file may be used under the terms of the GNU Lesser * + * General Public License version 2.1 as published by the Free Software * + * Foundation and appearing in the file LICENSE.LGPL included in the * + * packaging of this file. Please review the following information to * + * ensure the GNU Lesser General Public License version 2.1 requirements * + * will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. * + * * + ***************************************************************************/ +#ifndef WKBPTR_H +#define WKBPTR_H + +#include + +union wkbPtr +{ + void *vPtr; + double *dPtr; + int *iPtr; + unsigned char *ucPtr; + char *cPtr; + +}; + +const int SDO_ARRAY_SIZE = 1024; + +#define SDO_GTYPE_D(g) (g/1000%10) +#define SDO_GTYPE_L(g) (g/100%10) +#define SDO_GTYPE_TT(g) (g%100) +#define SDO_GTYPE(g,tt) (g*1000+tt) + +enum SDO_GTYPE_TT +{ + gtUnknown = 0, + gtPoint = 1, + gtLine = 2, + gtPolygon = 3, + gtCollection = 4, + gtMultiPoint = 5, + gtMultiLine = 6, + gtMultiPolygon = 7, +}; + + +class QOCISpatialGeometry : public QSharedData +{ + public: + bool isNull; + int gtype; + int srid; + double x, y, z; + + QVector eleminfo; + QVector ordinates; +}; + +Q_DECLARE_METATYPE( QOCISpatialGeometry ); + +#endif // WKBPTR_H diff --git a/src/providers/oracle/qgsoraclecolumntypethread.cpp b/src/providers/oracle/qgsoraclecolumntypethread.cpp new file mode 100644 index 000000000000..b2be870bd139 --- /dev/null +++ b/src/providers/oracle/qgsoraclecolumntypethread.cpp @@ -0,0 +1,66 @@ +/*************************************************************************** + qgscolumntypethread.cpp - lookup oracle geometry type and srid in a thread + ------------------- +begin : 3.1.2012 +copyright : (C) 2012 by Juergen E. Fischer +email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsoraclecolumntypethread.h" + +#include + +QgsOracleColumnTypeThread::QgsOracleColumnTypeThread( QgsOracleConn *conn, bool useEstimatedMetaData ) + : QThread() + , mConn( conn ) + , mUseEstimatedMetadata( useEstimatedMetaData ) +{ + qRegisterMetaType( "QgsOracleLayerProperty" ); +} + +void QgsOracleColumnTypeThread::addGeometryColumn( QgsOracleLayerProperty layerProperty ) +{ + layerProperties << layerProperty; +} + +void QgsOracleColumnTypeThread::stop() +{ + mStopped = true; +} + +void QgsOracleColumnTypeThread::run() +{ + if ( !mConn ) + return; + + mStopped = false; + + foreach ( QgsOracleLayerProperty layerProperty, layerProperties ) + { + if ( !mStopped ) + { + mConn->retrieveLayerTypes( layerProperty, mUseEstimatedMetadata ); + } + + if ( mStopped ) + { + layerProperty.types.clear(); + layerProperty.srids.clear(); + } + + // Now tell the layer list dialog box... + emit setLayerType( layerProperty ); + } + + mConn->disconnect(); + mConn = 0; +} diff --git a/src/providers/oracle/qgsoraclecolumntypethread.h b/src/providers/oracle/qgsoraclecolumntypethread.h new file mode 100644 index 000000000000..95c0f5e8ceaa --- /dev/null +++ b/src/providers/oracle/qgsoraclecolumntypethread.h @@ -0,0 +1,53 @@ +/*************************************************************************** + qgsoraclecolumntypethread.cpp - lookup oracle geometry type and srid in a thread + ------------------- + begin : 12.12.2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSORACLECOLUMNTYPETHREAD_H +#define QGSORACLECOLUMNTYPETHREAD_H + +#include +#include "qgsoracleconn.h" + +// A class that determines the geometry type of a given database +// schema.table.column, with the option of doing so in a separate +// thread. + +class QgsOracleColumnTypeThread : public QThread +{ + Q_OBJECT + public: + QgsOracleColumnTypeThread( QgsOracleConn *conn, bool useEstimatedMetaData ); + + // These functions get the layer types and pass that information out + // by emitting the setLayerType() signal. + virtual void run(); + + signals: + void setLayerType( QgsOracleLayerProperty layerProperty ); + + public slots: + void addGeometryColumn( QgsOracleLayerProperty layerProperty ); + void stop(); + + private: + QgsOracleColumnTypeThread() {} + + QgsOracleConn *mConn; + bool mUseEstimatedMetadata; + bool mStopped; + QList layerProperties; +}; + +#endif // QGSORACLECOLUMNTYPETHREAD_H diff --git a/src/providers/oracle/qgsoracleconn.cpp b/src/providers/oracle/qgsoracleconn.cpp new file mode 100644 index 000000000000..efe96b7fecd0 --- /dev/null +++ b/src/providers/oracle/qgsoracleconn.cpp @@ -0,0 +1,695 @@ +/*************************************************************************** + qgsoracleconn.cpp - connection class to Oracle + ------------------- + begin : August 2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsoracleconn.h" +#include "qgslogger.h" +#include "qgsdatasourceuri.h" +#include "qgsmessagelog.h" +#include "qgscredentials.h" +#include "qgsfield.h" +#include "qgsoracletablemodel.h" +#include "qgsdatasourceuri.h" + +#include +#include + +QMap QgsOracleConn::sConnections; +int QgsOracleConn::snConnections = 0; +const int QgsOracleConn::sGeomTypeSelectLimit = 100; + +QgsOracleConn *QgsOracleConn::connectDb( QgsDataSourceURI uri ) +{ + QString conninfo = uri.connectionInfo(); + + if ( sConnections.contains( conninfo ) ) + { + QgsDebugMsg( QString( "Using cached connection for %1" ).arg( conninfo ) ); + sConnections[conninfo]->mRef++; + return sConnections[conninfo]; + } + + QgsOracleConn *conn = new QgsOracleConn( uri ); + + if ( conn->mRef == 0 ) + { + delete conn; + return 0; + } + + sConnections.insert( conninfo, conn ); + + return conn; +} + +QgsOracleConn::QgsOracleConn( QgsDataSourceURI uri ) + : mRef( 1 ) + , mCurrentUser( QString::null ) + , mHasSpatial( -1 ) +{ + QgsDebugMsg( QString( "New Oracle connection for " ) + uri.connectionInfo() ); + + QString database = databaseName( uri.database(), uri.host(), uri.port() ); + QgsDebugMsg( QString( "New Oracle database " ) + database ); + + mDatabase = QSqlDatabase::addDatabase( "QOCISPATIAL", QString( "oracle%1" ).arg( snConnections++ ) ); + mDatabase.setDatabaseName( database ); + mDatabase.setUserName( uri.username() ); + mDatabase.setPassword( uri.password() ); + + if ( !mDatabase.open() ) + { + QString username = uri.username(); + QString password = uri.password(); + + while ( !mDatabase.open() ) + { + bool ok = QgsCredentials::instance()->get( database, username, password, mDatabase.lastError().text() ); + if ( !ok ) + break; + + if ( !username.isEmpty() ) + uri.setUsername( username ); + + if ( !password.isEmpty() ) + uri.setPassword( password ); + + QgsDebugMsg( "Connecting to " + database ); + mDatabase.setUserName( username ); + mDatabase.setPassword( password ); + } + + if ( mDatabase.isOpen() ) + QgsCredentials::instance()->put( database, username, password ); + } + + if ( !mDatabase.isOpen() ) + { + mDatabase.close(); + QgsMessageLog::logMessage( tr( "Connection to database failed" ), tr( "Oracle" ) ); + mRef = 0; + return; + } +} + +QgsOracleConn::~QgsOracleConn() +{ + Q_ASSERT( mRef == 0 ); + if ( mDatabase.isOpen() ) + mDatabase.close(); +} + +QString QgsOracleConn::connInfo() +{ + return sConnections.key( this, QString::null ); +} + +void QgsOracleConn::disconnect() +{ + if ( --mRef > 0 ) + return; + + QString key = sConnections.key( this, QString::null ); + + if ( !key.isNull() ) + { + sConnections.remove( key ); + } + else + { + QgsDebugMsg( "Connection not found" ); + } + + deleteLater(); +} + +bool QgsOracleConn::exec( QSqlQuery &qry, QString sql ) +{ + QgsDebugMsgLevel( QString( "SQL: %1" ).arg( sql ), 4 ); + + bool res = qry.exec( sql ); + if ( !res ) + { + QgsDebugMsg( QString( "SQL: %1\nERROR: %2" ) + .arg( qry.lastQuery() ) + .arg( qry.lastError().text() ) ); + } + + return res; +} + +QStringList QgsOracleConn::pkCandidates( QString ownerName, QString viewName ) +{ + QStringList cols; + + QSqlQuery qry( mDatabase ); + if ( !exec( qry, QString( "SELECT column_name FROM all_tab_columns WHERE owner=%1 AND table_name=%2 AND data_type='NUMBER' AND data_scale=0" ) + .arg( quotedValue( ownerName ) ).arg( quotedValue( viewName ) ) ) ) + { + QgsMessageLog::logMessage( tr( "SQL:%1\nerror:%2\n" ).arg( qry.lastQuery() ).arg( qry.lastError().text() ), tr( "Oracle" ) ); + return cols; + } + + while ( qry.next() ) + { + cols << qry.value( 0 ).toString(); + } + + qry.finish(); + + return cols; +} + +bool QgsOracleConn::tableInfo( bool geometryTablesOnly, bool userTablesOnly, bool allowGeometrylessTables ) +{ + QgsDebugMsg( "Entering." ); + + mLayersSupported.clear(); + + QString sql, delim; + + sql = QString( "SELECT c.owner,c.table_name,c.column_name,%1,t.table_name AS isview" + " FROM %2 c" + " LEFT OUTER JOIN all_tables t ON c.owner=t.owner AND c.table_name=t.table_name%3" ) + .arg( geometryTablesOnly ? "c.srid" : "NULL AS srid" ) + .arg( geometryTablesOnly ? "all_sdo_geom_metadata" : "all_tab_columns" ) + .arg( geometryTablesOnly ? "" : " WHERE c.data_type='SDO_GEOMETRY' AND c.data_type_owner='MDSYS'" ); + + if ( allowGeometrylessTables ) + { + sql += " UNION SELECT owner,table_name,NULL AS column_name,NULL AS srid,table_name AS isview FROM all_tables" + " UNION SELECT owner,view_name,NULL AS column_name,NULL AS srid,NULL AS isview FROM all_views"; + } + + sql = "SELECT * FROM (" + sql + ")"; + + if ( userTablesOnly ) + { + sql += " WHERE owner=user"; + } + + sql += " ORDER BY owner,isview,table_name,column_name"; + + QSqlQuery qry( mDatabase ); + if ( !exec( qry, sql ) ) + { + QgsMessageLog::logMessage( tr( "Querying available tables failed.\nSQL:%1\nerror:%2\n" ).arg( qry.lastQuery() ).arg( qry.lastError().text() ), tr( "Oracle" ) ); + return false; + } + + while ( qry.next() ) + { + QgsOracleLayerProperty layerProperty; + layerProperty.ownerName = qry.value( 0 ).toString(); + layerProperty.tableName = qry.value( 1 ).toString(); + layerProperty.geometryColName = qry.value( 2 ).toString(); + layerProperty.srids = QList() << qry.value( 3 ).toInt(); + layerProperty.types = QList() << QGis::WKBUnknown; // detect + layerProperty.isView = qry.value( 4 ).isNull(); + layerProperty.pkCols.clear(); + + mLayersSupported << layerProperty; + } + + if ( mLayersSupported.size() == 0 ) + { + QgsMessageLog::logMessage( tr( "Database connection was successful, but the accessible tables could not be determined." ), tr( "Oracle" ) ); + } + + return true; +} + +bool QgsOracleConn::supportedLayers( QVector &layers, bool geometryTablesOnly, bool userTablesOnly, bool allowGeometrylessTables ) +{ + // Get the list of supported tables + if ( !tableInfo( geometryTablesOnly, userTablesOnly, allowGeometrylessTables ) ) + { + QgsMessageLog::logMessage( tr( "Unable to get list of spatially enabled tables from the database" ), tr( "Oracle" ) ); + return false; + } + + layers = mLayersSupported; + + QgsDebugMsg( "Exiting." ); + + return true; +} + +QString QgsOracleConn::quotedIdentifier( QString ident ) +{ + ident.replace( '"', "\"\"" ); + ident = ident.prepend( "\"" ).append( "\"" ); + return ident; +} + +QString QgsOracleConn::quotedValue( QVariant value ) +{ + if ( value.isNull() ) + return "NULL"; + + switch ( value.type() ) + { + case QVariant::Int: + case QVariant::LongLong: + case QVariant::Double: + return value.toString(); + + default: + case QVariant::String: + QString v = value.toString(); + v.replace( "'", "''" ); + v.replace( "\\\"", "\\\\\"" ); + return v.prepend( "'" ).append( "'" ); + } +} + +QString QgsOracleConn::fieldExpression( const QgsField &fld ) +{ +#if 0 + const QString &type = fld.typeName(); + if ( type == "money" ) + { + return QString( "cash_out(%1)" ).arg( quotedIdentifier( fld.name() ) ); + } + else if ( type.startsWith( "_" ) ) + { + return QString( "array_out(%1)" ).arg( quotedIdentifier( fld.name() ) ); + } + else if ( type == "bool" ) + { + return QString( "boolout(%1)" ).arg( quotedIdentifier( fld.name() ) ); + } + else if ( type == "geometry" ) + { + return QString( "%1(%2)" ) + .arg( majorVersion() < 2 ? "asewkt" : "st_asewkt" ) + .arg( quotedIdentifier( fld.name() ) ); + } + else if ( type == "geography" ) + { + return QString( "st_astext(%1)" ).arg( quotedIdentifier( fld.name() ) ); + } + else + { + return quotedIdentifier( fld.name() ) + "::text"; + } +#else + return quotedIdentifier( fld.name() ); +#endif +} + +void QgsOracleConn::retrieveLayerTypes( QgsOracleLayerProperty &layerProperty, bool useEstimatedMetadata ) +{ + QgsDebugMsg( "entering: " + layerProperty.toString() ); + QString table; + QString where; + + if ( useEstimatedMetadata ) + { + table = QString( "(SELECT %1 FROM %2.%3 WHERE %1 IS NOT NULL%4 AND rownum<=%5)" ) + .arg( quotedIdentifier( layerProperty.geometryColName ) ) + .arg( quotedIdentifier( layerProperty.ownerName ) ) + .arg( quotedIdentifier( layerProperty.tableName ) ) + .arg( layerProperty.sql.isEmpty() ? "" : QString( " AND (%1)" ).arg( layerProperty.sql ) ) + .arg( sGeomTypeSelectLimit ); + } + else if ( !layerProperty.ownerName.isEmpty() ) + { + table = QString( "%1.%2" ) + .arg( quotedIdentifier( layerProperty.ownerName ) ) + .arg( quotedIdentifier( layerProperty.tableName ) ); + where = layerProperty.sql; + } + else + { + table = quotedIdentifier( layerProperty.tableName ); + where = layerProperty.sql; + } + + QSqlQuery qry( mDatabase ); + QString sql = QString( "SELECT DISTINCT t.%1.SDO_GTYPE,t.%1.SDO_SRID FROM %2 t WHERE NOT t.%1 IS NULL%3" ) + .arg( quotedIdentifier( layerProperty.geometryColName ) ) + .arg( table ) + .arg( where.isEmpty() ? "" : QString( " AND (%1)" ).arg( where ) ); + if ( !exec( qry, sql ) ) + { + QgsMessageLog::logMessage( tr( "SQL:%1\nerror:%2\n" ) + .arg( qry.lastQuery() ) + .arg( qry.lastError().text() ), + tr( "Oracle" ) ); + return; + } + + layerProperty.types.clear(); + layerProperty.srids.clear(); + + QSet srids; + while ( qry.next() ) + { + QGis::WkbType type = wkbTypeFromDatabase( qry.value( 0 ).toInt() ); + if ( type == QGis::WKBUnknown ) + { + QgsMessageLog::logMessage( tr( "Unsupported geometry type %1 in %2.%3.%4 ignored" ) + .arg( qry.value( 0 ).toInt() ) + .arg( layerProperty.ownerName ).arg( layerProperty.tableName ).arg( layerProperty.geometryColName ), + tr( "Oracle" ) ); + continue; + } + QgsDebugMsg( QString( "add type %1" ).arg( type ) ); + layerProperty.types << type; + layerProperty.srids << ( qry.value( 1 ).isNull() ? 0 : qry.value( 1 ).toInt() ); + srids << ( qry.value( 1 ).isNull() ? 0 : qry.value( 1 ).toInt() ); + } + + qry.finish(); + + if ( srids.size() == 1 ) + { + layerProperty.types << QGis::WKBUnknown; + layerProperty.srids << *srids.constBegin(); + } + + if ( layerProperty.isView ) + { + layerProperty.pkCols = pkCandidates( layerProperty.ownerName, layerProperty.tableName ); + if ( layerProperty.pkCols.isEmpty() ) + { + QgsMessageLog::logMessage( tr( "View %1.%2 doesn't have integer columns for use as keys." ) + .arg( layerProperty.ownerName ).arg( layerProperty.tableName ), + tr( "Oracle" ) ); + } + } + + QgsDebugMsg( "leaving." ); +} + +QString QgsOracleConn::databaseTypeFilter( QString alias, QString geomCol, QGis::WkbType geomType ) +{ + geomCol = quotedIdentifier( alias ) + "." + quotedIdentifier( geomCol ); + + switch ( geomType ) + { + case QGis::WKBPoint: + case QGis::WKBPoint25D: + case QGis::WKBMultiPoint: + case QGis::WKBMultiPoint25D: + return QString( "mod(%1.sdo_gtype,100) IN (1,5)" ).arg( geomCol ); + case QGis::WKBLineString: + case QGis::WKBLineString25D: + case QGis::WKBMultiLineString: + case QGis::WKBMultiLineString25D: + return QString( "mod(%1.sdo_gtype,100) IN (2,6)" ).arg( geomCol ); + case QGis::WKBPolygon: + case QGis::WKBPolygon25D: + case QGis::WKBMultiPolygon: + case QGis::WKBMultiPolygon25D: + return QString( "mod(%1.sdo_gtype,100) IN (3,7)" ).arg( geomCol ); + case QGis::WKBNoGeometry: + return QString( "%1 IS NULL" ).arg( geomCol ); + case QGis::WKBUnknown: + Q_ASSERT( !"unknown geometry unexpected" ); + return QString::null; + } + + Q_ASSERT( !"unexpected geomType" ); + return QString::null; +} + + +QGis::WkbType QgsOracleConn::wkbTypeFromDatabase( int gtype ) +{ + QgsDebugMsg( QString( "entering %1" ).arg( gtype ) ); + int t = gtype % 100; + + if ( t == 0 ) + return QGis::WKBUnknown; + + int d = gtype / 1000; + if ( d == 2 ) + { + switch ( t ) + { + case 1: + return QGis::WKBPoint; + case 2: + return QGis::WKBLineString; + case 3: + return QGis::WKBPolygon; + case 4: + QgsDebugMsg( QString( "geometry collection type %1 unsupported" ).arg( gtype ) ); + return QGis::WKBUnknown; + case 5: + return QGis::WKBMultiPoint; + case 6: + return QGis::WKBMultiLineString; + case 7: + return QGis::WKBMultiPolygon; + default: + QgsDebugMsg( QString( "gtype %1 unsupported" ).arg( gtype ) ); + return QGis::WKBUnknown; + } + } + else if ( d == 3 ) + { + switch ( t ) + { + case 1: + return QGis::WKBPoint25D; + case 2: + return QGis::WKBLineString25D; + case 3: + return QGis::WKBPolygon25D; + case 4: + QgsDebugMsg( QString( "geometry collection type %1 unsupported" ).arg( gtype ) ); + return QGis::WKBUnknown; + case 5: + return QGis::WKBMultiPoint25D; + case 6: + return QGis::WKBMultiLineString25D; + case 7: + return QGis::WKBMultiPolygon25D; + default: + QgsDebugMsg( QString( "gtype %1 unsupported" ).arg( gtype ) ); + return QGis::WKBUnknown; + } + } + else + { + QgsDebugMsg( QString( "dimension of gtype %1 unsupported" ).arg( gtype ) ); + return QGis::WKBUnknown; + } +} + +QString QgsOracleConn::displayStringForWkbType( QGis::WkbType type ) +{ + switch ( type ) + { + case QGis::WKBPoint: + case QGis::WKBPoint25D: + return tr( "Point" ); + + case QGis::WKBMultiPoint: + case QGis::WKBMultiPoint25D: + return tr( "Multipoint" ); + + case QGis::WKBLineString: + case QGis::WKBLineString25D: + return tr( "Line" ); + + case QGis::WKBMultiLineString: + case QGis::WKBMultiLineString25D: + return tr( "Multiline" ); + + case QGis::WKBPolygon: + case QGis::WKBPolygon25D: + return tr( "Polygon" ); + + case QGis::WKBMultiPolygon: + case QGis::WKBMultiPolygon25D: + return tr( "Multipolygon" ); + + case QGis::WKBNoGeometry: + return tr( "No Geometry" ); + + case QGis::WKBUnknown: + return tr( "Unknown Geometry" ); + } + + Q_ASSERT( !"unexpected wkbType" ); + return QString::null; +} + +QGis::WkbType QgsOracleConn::wkbTypeFromGeomType( QGis::GeometryType geomType ) +{ + switch ( geomType ) + { + case QGis::Point: + return QGis::WKBPoint; + case QGis::Line: + return QGis::WKBLineString; + case QGis::Polygon: + return QGis::WKBPolygon; + case QGis::NoGeometry: + return QGis::WKBNoGeometry; + case QGis::UnknownGeometry: + return QGis::WKBUnknown; + } + + Q_ASSERT( !"unexpected geomType" ); + return QGis::WKBUnknown; +} + +QStringList QgsOracleConn::connectionList() +{ + QSettings settings; + settings.beginGroup( "/Oracle/connections" ); + return settings.childGroups(); +} + +void QgsOracleConn::deleteConnection( QString theConnName ) +{ + QSettings settings; + + QString key = "/Oracle/connections/" + theConnName; + settings.remove( key + "/host" ); + settings.remove( key + "/port" ); + settings.remove( key + "/database" ); + settings.remove( key + "/username" ); + settings.remove( key + "/password" ); + settings.remove( key + "/publicOnly" ); + settings.remove( key + "/geometryColumnsOnly" ); + settings.remove( key + "/allowGeometrylessTables" ); + settings.remove( key + "/estimatedMetadata" ); + settings.remove( key + "/saveUsername" ); + settings.remove( key + "/savePassword" ); + settings.remove( key + "/save" ); + settings.remove( key ); +} + +QString QgsOracleConn::selectedConnection() +{ + QSettings settings; + return settings.value( "/Oracle/connections/selected" ).toString(); +} + +void QgsOracleConn::setSelectedConnection( QString name ) +{ + QSettings settings; + return settings.setValue( "/Oracle/connections/selected", name ); +} + +QgsDataSourceURI QgsOracleConn::connUri( QString theConnName ) +{ + QgsDebugMsg( "theConnName = " + theConnName ); + + QSettings settings; + + QString key = "/Oracle/connections/" + theConnName; + + QString service = settings.value( key + "/service" ).toString(); + + QString host = settings.value( key + "/host" ).toString(); + QString port = settings.value( key + "/port" ).toString(); + if ( port.length() == 0 ) + { + port = "1521"; + } + + bool useEstimatedMetadata = settings.value( key + "/estimatedMetadata", false ).toBool(); + + QString username; + QString password; + if ( settings.value( key + "/saveUsername" ).toString() == "true" ) + { + username = settings.value( key + "/username" ).toString(); + } + + if ( settings.value( key + "/savePassword" ).toString() == "true" ) + { + password = settings.value( key + "/password" ).toString(); + } + + QgsDataSourceURI uri; + uri.setConnection( host, port, QString::null, username, password ); + uri.setUseEstimatedMetadata( useEstimatedMetadata ); + + return uri; +} + +bool QgsOracleConn::userTablesOnly( QString theConnName ) +{ + QSettings settings; + return settings.value( "/Oracle/connections/" + theConnName + "/userTablesOnly", false ).toBool(); +} + +bool QgsOracleConn::allowGeometrylessTables( QString theConnName ) +{ + QSettings settings; + return settings.value( "/Oracle/connections/" + theConnName + "/allowGeometrylessTables", false ).toBool(); +} + +QString QgsOracleConn::databaseName( QString serviceName, QString host, QString port ) +{ + QString db; + + if ( !host.isEmpty() ) + { + db += host; + + if ( !port.isEmpty() && port != "1521" ) + { + db += QString( ":%1" ).arg( port ); + } + + if ( !serviceName.isEmpty() ) + { + db += "/" + serviceName; + } + } + else if ( !serviceName.isEmpty() ) + { + db = serviceName; + } + + return db; +} + +bool QgsOracleConn::hasSpatial() +{ + if ( mHasSpatial == -1 ) + { + QSqlQuery qry( mDatabase ); + mHasSpatial = exec( qry, "SELECT 1 FROM v$option WHERE parameter='Spatial' AND value='TRUE'" ) && qry.next(); + } + + return mHasSpatial; +} + +QString QgsOracleConn::currentUser() +{ + if ( mCurrentUser.isNull() ) + { + QSqlQuery qry( mDatabase ); + if ( exec( qry, "SELECT user FROM dual" ) && qry.next() ) + { + mCurrentUser = qry.value( 0 ).toString(); + } + } + + return mCurrentUser; +} + +// vim: sw=2 : diff --git a/src/providers/oracle/qgsoracleconn.h b/src/providers/oracle/qgsoracleconn.h new file mode 100644 index 000000000000..752be1833986 --- /dev/null +++ b/src/providers/oracle/qgsoracleconn.h @@ -0,0 +1,180 @@ +/*************************************************************************** + qgsoracleconn.h - connection class to Oracle + ------------------- + begin : August 2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSORACLECONN_H +#define QGSORACLECONN_H + +#include +#include +#include +#include +#include +#include + +#include "qgis.h" +#include "qgsdatasourceuri.h" + +#include +#include + +class QgsField; + +// Oracle layer properties +struct QgsOracleLayerProperty +{ + QList types; + QList srids; + QString ownerName; + QString tableName; + QString geometryColName; + bool isView; + QStringList pkCols; + QString sql; + + int size() { Q_ASSERT( types.size() == srids.size() ); return types.size(); } + + QgsOracleLayerProperty at( int i ) + { + QgsOracleLayerProperty property; + + Q_ASSERT( i >= 0 && i < size() ); + + property.types << types.at( i ); + property.srids << srids.at( i ); + property.ownerName = ownerName; + property.tableName = tableName; + property.geometryColName = geometryColName; + property.isView = isView; + property.pkCols = pkCols; + property.sql = sql; + + return property; + } + +#if QGISDEBUG + QString toString() + { + QString typeString; + foreach ( QGis::WkbType type, types ) + { + if ( !typeString.isEmpty() ) + typeString += "|"; + typeString += QString::number( type ); + } + QString sridString; + foreach ( int srid, srids ) + { + if ( !sridString.isEmpty() ) + sridString += "|"; + sridString += QString::number( srid ); + } + + return QString( "%1.%2.%3 type=%4 srid=%5 view=%6 sql=%7" ) + .arg( ownerName ) + .arg( tableName ) + .arg( geometryColName ) + .arg( typeString ) + .arg( sridString ) + .arg( isView ? "yes" : "no" ) + .arg( sql ); + } +#endif +}; + +class QgsOracleConn : public QThread +{ + Q_OBJECT; + + public: + static QgsOracleConn *connectDb( QgsDataSourceURI uri ); + void disconnect(); + + /** Double quote a Oracle identifier for placement in a SQL string. + */ + static QString quotedIdentifier( QString ident ); + + /** Quote a value for placement in a SQL string. + */ + static QString quotedValue( QVariant value ); + + //! Get the list of usable to check + bool supportedLayers( QVector &layers, + bool geometryTablesOnly, + bool userTablesOnly = true, + bool allowGeometrylessTables = false ); + + void retrieveLayerTypes( QgsOracleLayerProperty &layerProperty, bool useEstimatedMetadata ); + + /** Gets information about the spatial tables */ + bool tableInfo( bool geometryTablesOnly, bool userTablesOnly, bool allowGeometrylessTables ); + + /** get primary key candidates (all int4 columns) */ + QStringList pkCandidates( QString ownerName, QString viewName ); + + QString fieldExpression( const QgsField &fld ); + + QString connInfo(); + + QString currentUser(); + + bool hasSpatial(); + + static const int sGeomTypeSelectLimit; + + static QString displayStringForWkbType( QGis::WkbType wkbType ); + static QGis::WkbType wkbTypeFromDatabase( int gtype ); + + static QString databaseTypeFilter( QString alias, QString geomCol, QGis::WkbType wkbType ); + + static QGis::WkbType wkbTypeFromGeomType( QGis::GeometryType geomType ); + + static QStringList connectionList(); + static QString selectedConnection(); + static void setSelectedConnection( QString theConnName ); + static QgsDataSourceURI connUri( QString theConnName ); + static bool userTablesOnly( QString theConnName ); + static bool allowGeometrylessTables( QString theConnName ); + static void deleteConnection( QString theConnName ); + static QString databaseName( QString serviceName, QString host, QString port ); + + operator QSqlDatabase() { return mDatabase; } + + private: + QgsOracleConn( QgsDataSourceURI uri ); + ~QgsOracleConn(); + + bool exec( QSqlQuery &qry, QString sql ); + + //! reference count + int mRef; + + QString mCurrentUser; + + //! has spatial + int mHasSpatial; + + QSqlDatabase mDatabase; + QSqlQuery mQuery; + + //! List of the supported layers + QVector mLayersSupported; + + static QMap sConnections; + static int snConnections; +}; + +#endif diff --git a/src/providers/oracle/qgsoracledataitems.cpp b/src/providers/oracle/qgsoracledataitems.cpp new file mode 100644 index 000000000000..28df814681f4 --- /dev/null +++ b/src/providers/oracle/qgsoracledataitems.cpp @@ -0,0 +1,473 @@ +/*************************************************************************** + qgsoracledataitems.cpp + --------------------- + begin : August 2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include "qgsoracledataitems.h" + +#include "qgsoracletablemodel.h" +#include "qgsoraclenewconnection.h" +#include "qgsoraclecolumntypethread.h" +#include "qgslogger.h" +#include "qgsdatasourceuri.h" +#include "qgsapplication.h" + +#include +#include + +QGISEXTERN bool deleteLayer( const QString& uri, QString& errCause ); + +// --------------------------------------------------------------------------- +QgsOracleConnectionItem::QgsOracleConnectionItem( QgsDataItem* parent, QString name, QString path ) + : QgsDataCollectionItem( parent, name, path ) + , mColumnTypeThread( 0 ) +{ + mIcon = QgsApplication::getThemeIcon( "mIconConnect.png" ); +} + +QgsOracleConnectionItem::~QgsOracleConnectionItem() +{ + stop(); +} + +void QgsOracleConnectionItem::stop() +{ + if ( mColumnTypeThread ) + { + mColumnTypeThread->stop(); + mColumnTypeThread->wait(); + delete mColumnTypeThread; + mColumnTypeThread = 0; + } +} + +void QgsOracleConnectionItem::refresh() +{ + QgsDebugMsg( "Entering." ); + + QApplication::setOverrideCursor( Qt::WaitCursor ); + + stop(); + + foreach ( QgsDataItem *child, mChildren ) + { + QgsDebugMsg( QString( "Deleting child: %1" ).arg( child->name() ) ); + deleteChildItem( child ); + } + + foreach ( QgsDataItem *item, createChildren() ) + { + QgsDebugMsg( QString( "Adding child %1" ).arg( item->name() ) ); + addChildItem( item, true ); + } + + QApplication::restoreOverrideCursor(); + + QgsDebugMsg( "Leaving." ); +} + +QVector QgsOracleConnectionItem::createChildren() +{ + QgsDebugMsg( "Entered" ); + QVector children; + QgsDataSourceURI uri = QgsOracleConn::connUri( mName ); + + mOwnerMap.clear(); + + mConn = QgsOracleConn::connectDb( uri.connectionInfo() ); + if ( !mConn ) + return children; + + QVector layerProperties; + if ( !mConn->supportedLayers( layerProperties, + QgsOracleConn::userTablesOnly( mName ), + QgsOracleConn::allowGeometrylessTables( mName ) ) ) + { + children.append( new QgsErrorItem( this, tr( "Failed to retrieve layers" ), mPath + "/error" ) ); + return children; + } + + if ( layerProperties.isEmpty() ) + { + children.append( new QgsErrorItem( this, tr( "No layers found." ), mPath + "/error" ) ); + return children; + } + + stop(); + + foreach ( QgsOracleLayerProperty layerProperty, layerProperties ) + { + QgsOracleOwnerItem *ownerItem = mOwnerMap.value( layerProperty.ownerName, 0 ); + if ( !ownerItem ) + { + ownerItem = new QgsOracleOwnerItem( this, layerProperty.ownerName, mPath + "/" + layerProperty.ownerName ); + children.append( ownerItem ); + mOwnerMap[ layerProperty.ownerName ] = ownerItem; + } + + if ( !layerProperty.geometryColName.isNull() ) + { + if ( layerProperty.types.contains( QGis::WKBUnknown ) || layerProperty.srids.isEmpty() ) + { + if ( !mColumnTypeThread ) + { + QgsOracleConn *conn = QgsOracleConn::connectDb( uri.connectionInfo() ); + if ( conn ) + { + mColumnTypeThread = new QgsOracleColumnTypeThread( conn, true /* use estimated metadata */ ); + + connect( mColumnTypeThread, SIGNAL( setLayerType( QgsOracleLayerProperty ) ), + this, SLOT( setLayerType( QgsOracleLayerProperty ) ) ); + connect( this, SIGNAL( addGeometryColumn( QgsOracleLayerProperty ) ), + mColumnTypeThread, SLOT( addGeometryColumn( QgsOracleLayerProperty ) ) ); + } + } + + emit addGeometryColumn( layerProperty ); + } + } + else + { + ownerItem->addLayer( layerProperty ); + } + } + + if ( mColumnTypeThread ) + mColumnTypeThread->start(); + + return children; +} + +void QgsOracleConnectionItem::setLayerType( QgsOracleLayerProperty layerProperty ) +{ + QgsDebugMsg( layerProperty.toString() ); + + QgsOracleOwnerItem *ownerItem = mOwnerMap.value( layerProperty.ownerName, 0 ); + if ( !ownerItem ) + { + QgsDebugMsg( QString( "owner item for %1 not found." ).arg( layerProperty.ownerName ) ); + return; + } + + for ( int i = 0 ; i < layerProperty.size(); i++ ) + { + QGis::WkbType wkbType = layerProperty.types.at( i ); + if ( wkbType == QGis::WKBUnknown ) + { + // skip any geometry entry + continue; + } + + ownerItem->addLayer( layerProperty.at( i ) ); + } +} + +bool QgsOracleConnectionItem::equal( const QgsDataItem *other ) +{ + if ( type() != other->type() ) + { + return false; + } + + const QgsOracleConnectionItem *o = qobject_cast( other ); + return ( mPath == o->mPath && mName == o->mName && o->connection() == connection() ); +} + +QList QgsOracleConnectionItem::actions() +{ + QList lst; + + QAction* actionEdit = new QAction( tr( "Edit..." ), this ); + connect( actionEdit, SIGNAL( triggered() ), this, SLOT( editConnection() ) ); + lst.append( actionEdit ); + + QAction* actionDelete = new QAction( tr( "Delete" ), this ); + connect( actionDelete, SIGNAL( triggered() ), this, SLOT( deleteConnection() ) ); + lst.append( actionDelete ); + + return lst; +} + +void QgsOracleConnectionItem::editConnection() +{ + QgsOracleNewConnection nc( NULL, mName ); + if ( nc.exec() ) + { + // the parent should be updated + mParent->refresh(); + } +} + +void QgsOracleConnectionItem::deleteConnection() +{ + QgsOracleConn::deleteConnection( mName ); + + // the parent should be updated + mParent->refresh(); +} + +bool QgsOracleConnectionItem::handleDrop( const QMimeData * data, Qt::DropAction ) +{ + if ( !QgsMimeDataUtils::isUriList( data ) ) + return false; + + // TODO: probably should show a GUI with settings etc + QgsDataSourceURI uri = QgsOracleConn::connUri( mName ); + + qApp->setOverrideCursor( Qt::WaitCursor ); + + QProgressDialog *progress = new QProgressDialog( tr( "Copying features..." ), tr( "Abort" ), 0, 0, 0 ); + progress->setWindowTitle( tr( "Import layer" ) ); + progress->setWindowModality( Qt::WindowModal ); + progress->show(); + + QStringList importResults; + bool hasError = false; + QgsMimeDataUtils::UriList lst = QgsMimeDataUtils::decodeUriList( data ); + foreach ( const QgsMimeDataUtils::Uri& u, lst ) + { + if ( u.layerType != "vector" ) + { + importResults.append( tr( "%1: Not a vector layer!" ).arg( u.name ) ); + hasError = true; // only vectors can be imported + continue; + } + + // open the source layer + QgsVectorLayer* srcLayer = new QgsVectorLayer( u.uri, u.name, u.providerKey ); + + if ( srcLayer->isValid() ) + { + uri.setDataSource( QString(), u.name.left( 30 ).toUpper(), "QGS_GEOMETRY" ); + uri.setWkbType( srcLayer->wkbType() ); + QString authid = srcLayer->crs().authid(); + if( authid.startsWith( "EPSG:", Qt::CaseInsensitive ) ) + { + uri.setSrid( authid.mid( 5 ) ); + } + QgsDebugMsg( "URI " + uri.uri() ); + QgsVectorLayerImport::ImportError err; + QString importError; + err = QgsVectorLayerImport::importLayer( srcLayer, uri.uri(), "oracle", &srcLayer->crs(), false, &importError, false, 0, progress ); + if ( err == QgsVectorLayerImport::NoError ) + importResults.append( tr( "%1: OK!" ).arg( u.name ) ); + else + { + importResults.append( QString( "%1: %2" ).arg( u.name ).arg( importError ) ); + hasError = true; + } + } + else + { + importResults.append( tr( "%1: OK!" ).arg( u.name ) ); + hasError = true; + } + + delete srcLayer; + } + + delete progress; + + qApp->restoreOverrideCursor(); + + if ( hasError ) + { + QMessageBox::warning( 0, tr( "Import to Oracle database" ), tr( "Failed to import some layers!\n\n" ) + importResults.join( "\n" ) ); + } + else + { + QMessageBox::information( 0, tr( "Import to Oracle database" ), tr( "Import was successful." ) ); + } + + refresh(); + + return true; +} + +// --------------------------------------------------------------------------- +QgsOracleLayerItem::QgsOracleLayerItem( QgsDataItem* parent, QString name, QString path, QgsLayerItem::LayerType layerType, QgsOracleLayerProperty layerProperty ) + : QgsLayerItem( parent, name, path, QString(), layerType, "oracle" ) + , mLayerProperty( layerProperty ) +{ + mUri = createUri(); + mPopulated = true; +} + +QgsOracleLayerItem::~QgsOracleLayerItem() +{ +} + +QList QgsOracleLayerItem::actions() +{ + QList lst; + + QAction* actionDeleteLayer = new QAction( tr( "Delete layer" ), this ); + connect( actionDeleteLayer, SIGNAL( triggered() ), this, SLOT( deleteLayer() ) ); + lst.append( actionDeleteLayer ); + + return lst; +} + +void QgsOracleLayerItem::deleteLayer() +{ + QString errCause; + bool res = ::deleteLayer( mUri, errCause ); + if ( !res ) + { + QMessageBox::warning( 0, tr( "Delete layer" ), errCause ); + } + else + { + QMessageBox::information( 0, tr( "Delete layer" ), tr( "Layer deleted successfully." ) ); + mParent->refresh(); + } +} + +QString QgsOracleLayerItem::createUri() +{ + QString pkColName = mLayerProperty.pkCols.size() > 0 ? mLayerProperty.pkCols.at( 0 ) : QString::null; + + QgsOracleConnectionItem *connItem = qobject_cast( parent() ? parent()->parent() : 0 ); + + if ( !connItem ) + { + QgsDebugMsg( "connection item not found." ); + return QString::null; + } + + QgsDebugMsg( QString( "connInfo: %1" ).arg( connItem->connection()->connInfo() ) ); + + QgsDataSourceURI uri( connItem->connection()->connInfo() ); + uri.setDataSource( mLayerProperty.ownerName, mLayerProperty.tableName, mLayerProperty.geometryColName, mLayerProperty.sql, QString::null ); + Q_ASSERT( mLayerProperty.size() == 1 ); + uri.setSrid( QString::number( mLayerProperty.srids.at( 0 ) ) ); + uri.setWkbType( mLayerProperty.types.at( 0 ) ); + QgsDebugMsg( QString( "layer uri: %1" ).arg( uri.uri() ) ); + return uri.uri(); +} + +// --------------------------------------------------------------------------- +QgsOracleOwnerItem::QgsOracleOwnerItem( QgsDataItem* parent, QString name, QString path ) + : QgsDataCollectionItem( parent, name, path ) +{ + mIcon = QgsApplication::getThemeIcon( "mIconDbOwner.png" ); +} + +QVector QgsOracleOwnerItem::createChildren() +{ + QgsDebugMsg( "Entering." ); + return QVector(); +} + +QgsOracleOwnerItem::~QgsOracleOwnerItem() +{ +} + +void QgsOracleOwnerItem::addLayer( QgsOracleLayerProperty layerProperty ) +{ + QgsDebugMsg( layerProperty.toString() ); + + Q_ASSERT( layerProperty.size() == 1 ); + QGis::WkbType wkbType = layerProperty.types.at( 0 ); + QString tip = tr( "%1 as %2 in %3" ).arg( layerProperty.geometryColName ).arg( QgsOracleConn::displayStringForWkbType( wkbType ) ).arg( layerProperty.srids.at( 0 ) ); + + QgsLayerItem::LayerType layerType; + switch ( wkbType ) + { + case QGis::WKBPoint: + case QGis::WKBPoint25D: + case QGis::WKBMultiPoint: + case QGis::WKBMultiPoint25D: + layerType = QgsLayerItem::Point; + break; + case QGis::WKBLineString: + case QGis::WKBLineString25D: + case QGis::WKBMultiLineString: + case QGis::WKBMultiLineString25D: + layerType = QgsLayerItem::Line; + break; + case QGis::WKBPolygon: + case QGis::WKBPolygon25D: + case QGis::WKBMultiPolygon: + case QGis::WKBMultiPolygon25D: + layerType = QgsLayerItem::Polygon; + break; + default: + if ( wkbType == QGis::WKBNoGeometry && layerProperty.geometryColName.isEmpty() ) + { + layerType = QgsLayerItem::TableLayer; + tip = tr( "as geometryless table" ); + } + else + { + return; + } + } + + QgsOracleLayerItem *layerItem = new QgsOracleLayerItem( this, layerProperty.tableName, mPath + "/" + layerProperty.tableName, layerType, layerProperty ); + layerItem->setToolTip( tip ); + addChild( layerItem ); +} + +// --------------------------------------------------------------------------- +QgsOracleRootItem::QgsOracleRootItem( QgsDataItem* parent, QString name, QString path ) + : QgsDataCollectionItem( parent, name, path ) +{ + mIcon = QgsApplication::getThemeIcon( "mIconOracle.png" ); + populate(); +} + +QgsOracleRootItem::~QgsOracleRootItem() +{ +} + +QVector QgsOracleRootItem::createChildren() +{ + QVector connections; + foreach ( QString connName, QgsOracleConn::connectionList() ) + { + connections << new QgsOracleConnectionItem( this, connName, mPath + "/" + connName ); + } + return connections; +} + +QList QgsOracleRootItem::actions() +{ + QList lst; + + QAction* actionNew = new QAction( tr( "New Connection..." ), this ); + connect( actionNew, SIGNAL( triggered() ), this, SLOT( newConnection() ) ); + lst.append( actionNew ); + + return lst; +} + +QWidget *QgsOracleRootItem::paramWidget() +{ + QgsOracleSourceSelect *select = new QgsOracleSourceSelect( 0, 0, true, true ); + connect( select, SIGNAL( connectionsChanged() ), this, SLOT( connectionsChanged() ) ); + return select; +} + +void QgsOracleRootItem::connectionsChanged() +{ + refresh(); +} + +void QgsOracleRootItem::newConnection() +{ + QgsOracleNewConnection nc( NULL ); + if ( nc.exec() ) + { + refresh(); + } +} diff --git a/src/providers/oracle/qgsoracledataitems.h b/src/providers/oracle/qgsoracledataitems.h new file mode 100644 index 000000000000..0a8896f29558 --- /dev/null +++ b/src/providers/oracle/qgsoracledataitems.h @@ -0,0 +1,117 @@ +/*************************************************************************** + qgsoracledataitems.h + --------------------- + begin : August 2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSORACLEDATAITEMS_H +#define QGSORACLEDATAITEMS_H + +#include + +#include "qgsdataitem.h" + +#include "qgsoracletablemodel.h" +#include "qgsoraclesourceselect.h" +#include "qgsmimedatautils.h" +#include "qgsvectorlayerimport.h" + +class QSqlDatabase; + +class QgsOracleRootItem; +class QgsOracleConnectionItem; +class QgsOracleOwnerItem; +class QgsOracleLayerItem; + +class QgsOracleRootItem : public QgsDataCollectionItem +{ + Q_OBJECT + public: + QgsOracleRootItem( QgsDataItem* parent, QString name, QString path ); + ~QgsOracleRootItem(); + + QVector createChildren(); + + virtual QWidget * paramWidget(); + + virtual QList actions(); + + public slots: + void connectionsChanged(); + void newConnection(); +}; + +class QgsOracleConnectionItem : public QgsDataCollectionItem +{ + Q_OBJECT + public: + QgsOracleConnectionItem( QgsDataItem* parent, QString name, QString path ); + ~QgsOracleConnectionItem(); + + QVector createChildren(); + virtual bool equal( const QgsDataItem *other ); + virtual QList actions(); + + virtual bool acceptDrop() { return true; } + virtual bool handleDrop( const QMimeData * data, Qt::DropAction action ); + + QgsOracleConn *connection() const { return mConn; } + + void refresh(); + + signals: + void addGeometryColumn( QgsOracleLayerProperty ); + + public slots: + void editConnection(); + void deleteConnection(); + + void setLayerType( QgsOracleLayerProperty layerProperty ); + + private: + void stop(); + QgsOracleConn *mConn; + QMap mOwnerMap; + QgsOracleColumnTypeThread *mColumnTypeThread; +}; + +class QgsOracleOwnerItem : public QgsDataCollectionItem +{ + Q_OBJECT + public: + QgsOracleOwnerItem( QgsDataItem* parent, QString name, QString path ); + ~QgsOracleOwnerItem(); + + QVector createChildren(); + + void addLayer( QgsOracleLayerProperty layerProperty ); +}; + +class QgsOracleLayerItem : public QgsLayerItem +{ + Q_OBJECT + + public: + QgsOracleLayerItem( QgsDataItem* parent, QString name, QString path, QgsLayerItem::LayerType layerType, QgsOracleLayerProperty layerProperties ); + ~QgsOracleLayerItem(); + + QString createUri(); + + virtual QList actions(); + + public slots: + void deleteLayer(); + + private: + QgsOracleLayerProperty mLayerProperty; +}; + +#endif // QGSORACLEDATAITEMS_H diff --git a/src/providers/oracle/qgsoraclenewconnection.cpp b/src/providers/oracle/qgsoraclenewconnection.cpp new file mode 100644 index 000000000000..d5718db5395c --- /dev/null +++ b/src/providers/oracle/qgsoraclenewconnection.cpp @@ -0,0 +1,160 @@ +/*************************************************************************** + qgsoraclenewconnection.cpp - description + ------------------- + begin : August 2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include +#include +#include + +#include "qgsoraclenewconnection.h" +#include "qgscontexthelp.h" +#include "qgsdatasourceuri.h" +#include "qgsoracletablemodel.h" + +QgsOracleNewConnection::QgsOracleNewConnection( QWidget *parent, const QString& connName, Qt::WFlags fl ) + : QDialog( parent, fl ), mOriginalConnName( connName ) +{ + setupUi( this ); + + if ( !connName.isEmpty() ) + { + // populate the dialog with the information stored for the connection + // populate the fields with the stored setting parameters + QSettings settings; + + QString key = "/Oracle/connections/" + connName; + txtDatabase->setText( settings.value( key + "/database" ).toString() ); + txtHost->setText( settings.value( key + "/host" ).toString() ); + QString port = settings.value( key + "/port" ).toString(); + if ( port.length() == 0 ) + { + port = "1521"; + } + txtPort->setText( port ); + cb_userTablesOnly->setChecked( settings.value( key + "/userTablesOnly", false ).toBool() ); + cb_allowGeometrylessTables->setChecked( settings.value( key + "/allowGeometrylessTables", false ).toBool() ); + cb_useEstimatedMetadata->setChecked( settings.value( key + "/estimatedMetadata", false ).toBool() ); + + if ( settings.value( key + "/saveUsername" ).toString() == "true" ) + { + txtUsername->setText( settings.value( key + "/username" ).toString() ); + chkStoreUsername->setChecked( true ); + } + + if ( settings.value( key + "/savePassword" ).toString() == "true" ) + { + txtPassword->setText( settings.value( key + "/password" ).toString() ); + chkStorePassword->setChecked( true ); + } + + // Old save setting + if ( settings.contains( key + "/save" ) ) + { + txtUsername->setText( settings.value( key + "/username" ).toString() ); + chkStoreUsername->setChecked( !txtUsername->text().isEmpty() ); + + if ( settings.value( key + "/save" ).toString() == "true" ) + txtPassword->setText( settings.value( key + "/password" ).toString() ); + + chkStorePassword->setChecked( true ); + } + + txtName->setText( connName ); + } +} +/** Autoconnected SLOTS **/ +void QgsOracleNewConnection::accept() +{ + QSettings settings; + QString baseKey = "/Oracle/connections/"; + settings.setValue( baseKey + "selected", txtName->text() ); + + if ( chkStorePassword->isChecked() && + QMessageBox::question( this, + tr( "Saving passwords" ), + tr( "WARNING: You have opted to save your password. It will be stored in plain text in your project files and in your home directory on Unix-like systems, or in your user profile on Windows. If you do not want this to happen, please press the Cancel button.\n" ), + QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Cancel ) + { + return; + } + + // warn if entry was renamed to an existing connection + if (( mOriginalConnName.isNull() || mOriginalConnName != txtName->text() ) && + ( settings.contains( baseKey + txtName->text() + "/service" ) || + settings.contains( baseKey + txtName->text() + "/host" ) ) && + QMessageBox::question( this, + tr( "Save connection" ), + tr( "Should the existing connection %1 be overwritten?" ).arg( txtName->text() ), + QMessageBox::Ok | QMessageBox::Cancel ) == QMessageBox::Cancel ) + { + return; + } + + // on rename delete the original entry first + if ( !mOriginalConnName.isNull() && mOriginalConnName != txtName->text() ) + { + + settings.remove( baseKey + mOriginalConnName ); + } + + baseKey += txtName->text(); + settings.setValue( baseKey + "/database", txtDatabase->text() ); + settings.setValue( baseKey + "/host", txtHost->text() ); + settings.setValue( baseKey + "/port", txtPort->text() ); + settings.setValue( baseKey + "/username", chkStoreUsername->isChecked() ? txtUsername->text() : "" ); + settings.setValue( baseKey + "/password", chkStorePassword->isChecked() ? txtPassword->text() : "" ); + settings.setValue( baseKey + "/userTablesOnly", cb_userTablesOnly->isChecked() ); + settings.setValue( baseKey + "/allowGeometrylessTables", cb_allowGeometrylessTables->isChecked() ); + settings.setValue( baseKey + "/saveUsername", chkStoreUsername->isChecked() ? "true" : "false" ); + settings.setValue( baseKey + "/savePassword", chkStorePassword->isChecked() ? "true" : "false" ); + settings.setValue( baseKey + "/estimatedMetadata", cb_useEstimatedMetadata->isChecked() ? "true" : "false" ); + + // remove old save setting + settings.remove( baseKey + "/save" ); + + QDialog::accept(); +} + +void QgsOracleNewConnection::on_btnConnect_clicked() +{ + QgsDataSourceURI uri; + uri.setConnection( txtHost->text(), txtPort->text(), txtDatabase->text(), txtUsername->text(), txtPassword->text() ); + + QgsOracleConn *conn = QgsOracleConn::connectDb( uri ); + + if ( conn ) + { + // Database successfully opened; we can now issue SQL commands. + QMessageBox::information( this, + tr( "Test connection" ), + tr( "Connection to %1 was successful" ).arg( txtDatabase->text() ) ); + + // free connection resources + conn->disconnect(); + } + else + { + QMessageBox::information( this, + tr( "Test connection" ), + tr( "Connection failed - Check settings and try again.\n\n" ) ); + } +} + +/** end Autoconnected SLOTS **/ + +QgsOracleNewConnection::~QgsOracleNewConnection() +{ +} diff --git a/src/providers/oracle/qgsoraclenewconnection.h b/src/providers/oracle/qgsoraclenewconnection.h new file mode 100644 index 000000000000..1febaef764f3 --- /dev/null +++ b/src/providers/oracle/qgsoraclenewconnection.h @@ -0,0 +1,43 @@ +/*************************************************************************** + qgsoraclenewconnection.h - description + ------------------- + begin : August 2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSORACLENEWCONNECTION_H +#define QGSORACLENEWCONNECTION_H +#include "ui_qgsoraclenewconnectionbase.h" +#include "qgisgui.h" +#include "qgscontexthelp.h" + +/*! \class QgsOracleNewConnection + * \brief Dialog to allow the user to configure and save connection + * information for a Oracle database + */ +class QgsOracleNewConnection : public QDialog, private Ui::QgsOracleNewConnectionBase +{ + Q_OBJECT + public: + //! Constructor + QgsOracleNewConnection( QWidget *parent = 0, const QString& connName = QString::null, Qt::WFlags fl = QgisGui::ModalDialogFlags ); + //! Destructor + ~QgsOracleNewConnection(); + public slots: + void accept(); + void on_btnConnect_clicked(); + void on_buttonBox_helpRequested() { QgsContextHelp::run( metaObject()->className() ); } + private: + QString mOriginalConnName; //store initial name to delete entry in case of rename +}; + +#endif // QGSORACLENEWCONNECTIONBASE_H diff --git a/src/providers/oracle/qgsoracleprovider.cpp b/src/providers/oracle/qgsoracleprovider.cpp new file mode 100644 index 000000000000..c62ac8415bbc --- /dev/null +++ b/src/providers/oracle/qgsoracleprovider.cpp @@ -0,0 +1,3156 @@ +/*************************************************************************** + qgsoracleprovider.cpp - QGIS data provider for Oracle layers + ------------------- + begin : August 2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsapplication.h" +#include "qgsfeature.h" +#include "qgsfield.h" +#include "qgsgeometry.h" +#include "qgsmessageoutput.h" +#include "qgsmessagelog.h" +#include "qgsrectangle.h" +#include "qgscoordinatereferencesystem.h" +#include "qgsvectorlayerimport.h" +#include "qgslogger.h" + +#include "qgsprovidercountcalcevent.h" +#include "qgsproviderextentcalcevent.h" + +#include "qgsoracleprovider.h" +#include "qgsoracletablemodel.h" +#include "qgsoraclesourceselect.h" +#include "qgsoracledataitems.h" + +#include +#include + +const QString ORACLE_KEY = "oracle"; +const QString ORACLE_DESCRIPTION = "Oracle data provider"; + +QgsOracleProvider::QgsOracleProvider( QString const & uri ) + : QgsVectorDataProvider( uri ) + , mValid( false ) + , mPrimaryKeyType( pktUnknown ) + , mDetectedGeomType( QGis::WKBUnknown ) + , mRequestedGeomType( QGis::WKBUnknown ) + , mSpatialIndex( QString::null ) + , mFidCounter( 0 ) +{ + static int geomMetaType = -1; + if ( geomMetaType < 0 ) + geomMetaType = qRegisterMetaType(); + + QgsDebugMsg( QString( "URI: %1 " ).arg( uri ) ); + + mUri = QgsDataSourceURI( uri ); + + // populate members from the uri structure + mOwnerName = mUri.schema(); + mTableName = mUri.table(); + mGeometryColumn = mUri.geometryColumn(); + mSqlWhereClause = mUri.sql(); + mSrid = mUri.srid().toInt(); + mRequestedGeomType = mUri.wkbType(); + + mConnection = QgsOracleConn::connectDb( mUri.connectionInfo() ); + if ( !mConnection ) + { + return; + } + + if ( mOwnerName.isEmpty() && mTableName.startsWith( "(" ) && mTableName.endsWith( ")" ) ) + { + mIsQuery = true; + mQuery = mTableName; + mTableName = ""; + } + else + { + mIsQuery = false; + + if ( mOwnerName.isEmpty() ) + { + mOwnerName = mConnection->currentUser(); + } + + if ( !mOwnerName.isEmpty() ) + { + mQuery += quotedIdentifier( mOwnerName ) + "."; + } + + if ( !mTableName.isEmpty() ) + { + mQuery += quotedIdentifier( mTableName ); + } + } + + QgsDebugMsg( QString( "Connection info is %1" ).arg( mUri.connectionInfo() ) ); + QgsDebugMsg( QString( "Geometry column is: %1" ).arg( mGeometryColumn ) ); + QgsDebugMsg( QString( "Owner is: %1" ).arg( mOwnerName ) ); + QgsDebugMsg( QString( "Table name is: %1" ).arg( mTableName ) ); + QgsDebugMsg( QString( "Query is: %1" ).arg( mQuery ) ); + QgsDebugMsg( QString( "Where clause is: %1" ).arg( mSqlWhereClause ) ); + QgsDebugMsg( QString( "SRID is: %1" ).arg( mSrid ) ); + + // no table/query passed, the provider could be used to get tables + if ( mQuery.isEmpty() ) + { + return; + } + + if ( !hasSufficientPermsAndCapabilities() ) + { + return; + } + + // get geometry details + if ( !getGeometryDetails() ) // gets srid, geometry and data type + { + // the table is not a geometry table + mValid = false; + disconnectDb(); + return; + } + + mLayerExtent.setMinimal(); + mFeaturesCounted = -1; + + // set the primary key + if ( !determinePrimaryKey() ) + { + mValid = false; + disconnectDb(); + return; + } + + //fill type names into sets + mNativeTypes + // integer types + << QgsVectorDataProvider::NativeType( tr( "Whole number" ), "number(10,0)", QVariant::Int ) + << QgsVectorDataProvider::NativeType( tr( "Whole big number" ), "number(20,0)", QVariant::LongLong ) + << QgsVectorDataProvider::NativeType( tr( "Decimal number (numeric)" ), "number", QVariant::Double, 1, 38, 0, 38 ) + << QgsVectorDataProvider::NativeType( tr( "Decimal number (decimal)" ), "double precision", QVariant::Double ) + + // floating point + << QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), "binary_float", QVariant::Double ) + << QgsVectorDataProvider::NativeType( tr( "Decimal number (double)" ), "binary_double", QVariant::Double ) + + // string types + << QgsVectorDataProvider::NativeType( tr( "Text, fixed length (char)" ), "CHAR", QVariant::String, 1, 255 ) + << QgsVectorDataProvider::NativeType( tr( "Text, limited variable length (varchar2)" ), "VARCHAR2", QVariant::String, 1, 255 ) + << QgsVectorDataProvider::NativeType( tr( "Text, unlimited length (long)" ), "LONG", QVariant::String ) + ; + + QString key; + switch ( mPrimaryKeyType ) + { + case pktRowId: + key = "ROWID"; + break; + + case pktInt: + case pktFidMap: + { + Q_ASSERT( mPrimaryKeyType != pktInt || mPrimaryKeyAttrs.size() == 1 ); + + QString delim; + foreach ( int idx, mPrimaryKeyAttrs ) + { + Q_ASSERT( mAttributeFields.contains( mPrimaryKeyAttrs[idx] ) ); + key += delim + mAttributeFields[ idx ].name(); + delim = ","; + } + } + break; + case pktUnknown: + mValid = false; + break; + } + + if ( mValid ) + { + mUri.setKeyColumn( key ); + setDataSourceUri( mUri.uri() ); + } + else + { + disconnectDb(); + } +} + +QgsOracleProvider::~QgsOracleProvider() +{ + QgsDebugMsg( "deconstructing." ); + disconnectDb(); +} + +void QgsOracleProvider::disconnectDb() +{ + if ( mConnection ) + mConnection->disconnect(); + mConnection = 0; +} + +bool QgsOracleProvider::exec( QSqlQuery &qry, QString sql ) +{ + QgsDebugMsgLevel( QString( "SQL: %1" ).arg( sql ), 4 ); + + qry.setForwardOnly( true ); + + bool res = qry.exec( sql ); + if ( !res ) + { + QgsDebugMsg( QString( "SQL: %1\nERROR: %2" ) + .arg( qry.lastQuery() ) + .arg( qry.lastError().text() ) ); + } + + return res; +} + +QString QgsOracleProvider::storageType() const +{ + return "Oracle database with locator/spatial extension"; +} + +bool QgsOracleProvider::openQuery( + QString alias, + const QgsAttributeList &fetchAttributes, + bool fetchGeometry, + QString whereClause ) +{ + if ( fetchGeometry && mGeometryColumn.isNull() ) + { + return false; + } + + try + { + QString query = "SELECT ", delim = ""; + + if ( fetchGeometry ) + { + query += quotedIdentifier( mGeometryColumn ); + delim = ","; + } + + switch ( mPrimaryKeyType ) + { + case pktRowId: + query += delim + quotedIdentifier( "ROWID" ); + delim = ","; + break; + + case pktInt: + query += delim + quotedIdentifier( field( mPrimaryKeyAttrs[0] ).name() ); + delim = ","; + break; + + case pktFidMap: + foreach ( int idx, mPrimaryKeyAttrs ) + { + query += delim + mConnection->fieldExpression( field( idx ) ); + delim = ","; + } + break; + + case pktUnknown: + QgsDebugMsg( "Cannot query without primary key." ); + return false; + break; + } + + foreach ( int idx, fetchAttributes ) + { + if ( mPrimaryKeyAttrs.contains( idx ) ) + continue; + + query += delim + mConnection->fieldExpression( field( idx ) ); + } + + query += QString( " FROM %1 %2" ).arg( mQuery ).arg( quotedIdentifier( alias ) ); + + if ( !whereClause.isEmpty() ) + query += QString( " WHERE %1" ).arg( whereClause ); + + QgsDebugMsg( QString( "Fetch features: %1" ).arg( query ) ); + if ( !exec( mQry, query ) ) + { + QgsMessageLog::logMessage( tr( "Fetching features failed.\nSQL:%1\nError: %2" ) + .arg( mQry.lastQuery() ) + .arg( mQry.lastError().text() ), + tr( "Oracle" ) ); + + // reloading the fields might help next time around + rewind(); + return false; + } + } + catch ( OracleFieldNotFound ) + { + rewind(); + return false; + } + + return true; +} + +static bool operator<( const QVariant &a, const QVariant &b ) +{ + if ( a.isNull() || b.isNull() ) + return false; + + if ( a.type() == b.type() ) + { + switch ( a.type() ) + { + case QVariant::Int: + case QVariant::Char: + return a.toInt() < b.toInt(); + + case QVariant::Double: + return a.toDouble() < b.toDouble(); + + case QVariant::LongLong: + return a.toLongLong() < b.toLongLong(); + + case QVariant::List: + { + QList al = a.toList(); + QList bl = b.toList(); + + int i, n = qMin( al.size(), bl.size() ); + for ( i = 0; i < n && al[i] == bl[i]; i++ ) + ; + + if ( i == n ) + return al.size() < bl.size(); + else + return al[i] < bl[i]; + } + break; + + case QVariant::StringList: + { + QStringList al = a.toStringList(); + QStringList bl = b.toStringList(); + + int i, n = qMin( al.size(), bl.size() ); + for ( i = 0; i < n && al[i] == bl[i]; i++ ) + ; + + if ( i == n ) + return al.size() < bl.size(); + else + return al[i] < bl[i]; + } + break; + + case QVariant::Date: + return a.toDate() < b.toDate(); + + case QVariant::Time: + return a.toTime() < b.toTime(); + + case QVariant::DateTime: + return a.toDateTime() < b.toDateTime(); + + case QVariant::Bool: + return a.toBool() < b.toBool(); + + case QVariant::UInt: + return a.toUInt() < b.toUInt(); + + case QVariant::ULongLong: + return a.toULongLong() < b.toULongLong(); + + default: + break; + } + } + + return a.canConvert( QVariant::String ) && b.canConvert( QVariant::String ) && a.toString() < b.toString(); +} + +QgsFeatureId QgsOracleProvider::lookupFid( const QVariant &v ) +{ + QMap::const_iterator it = mKeyToFid.find( v ); + + if ( it != mKeyToFid.constEnd() ) + { + return it.value(); + } + + mFidToKey.insert( ++mFidCounter, v ); + mKeyToFid.insert( v, mFidCounter ); + + return mFidCounter; +} + +bool QgsOracleProvider::getFeature( bool fetchGeometry, + QgsFeature &feature, + const QgsAttributeList &fetchAttributes ) +{ + try + { + feature.clearAttributeMap(); + + int col = 0; + + if ( fetchGeometry ) + { + QByteArray *ba = static_cast( mQry.value( col++ ).data() ); + unsigned char *copy = new unsigned char[ba->size()]; + memcpy( copy, ba->constData(), ba->size() ); +#if 0 + QString dump; + for ( int i = 0; i < ba->size(); i++ ) + { + if ( i % 64 == 0 ) + { + if ( i > 0 ) + dump += "\n"; + dump += QString( "%1:" ).arg( i, 6 ); + } + dump += QString( " %1" ).arg(( unsigned int ) copy[i], 2, 16, QChar( '0' ) ); + } + dump += "\n"; + QgsDebugMsgLevel( dump, 3 ); +#endif + feature.setGeometryAndOwnership( copy, ba->size() ); + } + + QgsFeatureId fid = 0; + + switch ( mPrimaryKeyType ) + { + case pktInt: + // get 64bit integer from result + fid = mQry.value( col++ ).toLongLong(); + if ( mPrimaryKeyType == pktInt && fetchAttributes.contains( mPrimaryKeyAttrs[0] ) ) + feature.addAttribute( mPrimaryKeyAttrs[0], fid ); + break; + + case pktRowId: + case pktFidMap: + { + QList primaryKeyVals; + + if ( mPrimaryKeyType == pktFidMap ) + { + foreach ( int idx, mPrimaryKeyAttrs ) + { + const QgsField &fld = field( idx ); + + QVariant v = convertValue( fld.type(), mQry.value( col ).toString() ); + primaryKeyVals << v; + + if ( fetchAttributes.contains( idx ) ) + feature.addAttribute( idx, v ); + + col++; + } + } + else + { + primaryKeyVals << mQry.value( col++ ); + } + + fid = lookupFid( QVariant( primaryKeyVals ) ); + } + break; + + case pktUnknown: + Q_ASSERT( !"FAILURE: cannot get feature with unknown primary key" ); + return false; + } + + feature.setFeatureId( fid ); + QgsDebugMsgLevel( QString( "fid=%1" ).arg( fid ), 4 ); + + // iterate attributes + foreach ( int idx, fetchAttributes ) + { + if ( mPrimaryKeyAttrs.contains( idx ) ) + continue; + + const QgsField &fld = field( idx ); + + QVariant v = convertValue( fld.type(), mQry.value( col ).toString() ); + feature.addAttribute( idx, v ); + + col++; + } + + return true; + } + catch ( OracleFieldNotFound ) + { + return false; + } +} + +void QgsOracleProvider::select( QgsAttributeList fetchAttributes, QgsRectangle rect, bool fetchGeometry, bool useIntersect ) +{ + mFetchGeomRequested = fetchGeometry; + mIntersectResult = false; + + mQry = QSqlQuery( *mConnection ); + + QString whereClause; + + if ( !rect.isEmpty() && !mGeometryColumn.isNull() ) + { + QString bbox = QString( "mdsys.sdo_geometry(2003,%1,NULL," + "mdsys.sdo_elem_info_array(1,1003,3)," + "mdsys.sdo_ordinate_array(%2,%3,%4,%5)" + ")" ) + .arg( mSrid < 1 ? "NULL" : QString::number( mSrid ) ) + .arg( rect.xMinimum(), 0, 'f', 16 ) + .arg( rect.yMinimum(), 0, 'f', 16 ) + .arg( rect.xMaximum(), 0, 'f', 16 ) + .arg( rect.yMaximum(), 0, 'f', 16 ); + + if ( !mSpatialIndex.isNull() ) + { + whereClause = QString( "sdo_filter(%1,%2)='TRUE'" ).arg( quotedIdentifier( mGeometryColumn ) ).arg( bbox ); + + if ( useIntersect ) + { + whereClause += QString( " AND sdo_relate(%1,%2,'mask=ANYINTERACT')='TRUE'" ) + .arg( quotedIdentifier( mGeometryColumn ) ) + .arg( bbox ); +#if 0 + mIntersectResult = true; + mIntersectRect = rect; + fetchGeometry = true; // need to fetch the geometry +#endif + } + } + else + { + mIntersectResult = useIntersect; + mIntersectRect = rect; + fetchGeometry |= useIntersect; // need to fetch the geometry + } + } + + if ( mRequestedGeomType != QGis::WKBUnknown && mRequestedGeomType != mDetectedGeomType ) + { + if ( !whereClause.isEmpty() ) + whereClause += " AND "; + + whereClause += QgsOracleConn::databaseTypeFilter( "selectedFeatures", mGeometryColumn, mRequestedGeomType ); + } + + if ( !mSqlWhereClause.isEmpty() ) + { + if ( !whereClause.isEmpty() ) + whereClause += " AND "; + whereClause += "(" + mSqlWhereClause + ")"; + } + + mFetchGeom = fetchGeometry; + mAttributesToFetch = fetchAttributes; + + if ( !openQuery( "selectedFeatures", fetchAttributes, fetchGeometry, whereClause ) ) + return; +} + +bool QgsOracleProvider::nextFeature( QgsFeature& feature ) +{ + feature.setValid( false ); + if ( !mValid ) + { + QgsMessageLog::logMessage( tr( "Read attempt on an invalid oracle data source" ), tr( "Oracle" ) ); + return false; + } + + for ( ;; ) + { + if ( !mQry.next() || !getFeature( mFetchGeom, feature, mAttributesToFetch ) ) + { + return false; + } + + if ( mIntersectResult && ( !feature.geometry() || !feature.geometry()->intersects( mIntersectRect ) ) ) + { + // skip feature that don't intersect with our rectangle + QgsDebugMsg( "no intersect" ); + continue; + } + + // clear geometry if it wasn't requested + if ( mFetchGeom && !mFetchGeomRequested ) + feature.setGeometry( 0 ); + + feature.setValid( true ); + return true; + } +} + +QString QgsOracleProvider::pkParamWhereClause() const +{ + QString whereClause; + + switch ( mPrimaryKeyType ) + { + case pktInt: + case pktFidMap: + { + Q_ASSERT( mPrimaryKeyAttrs.size() >= 1 ); + + QString delim = ""; + for ( int i = 0; i < mPrimaryKeyAttrs.size(); i++ ) + { + int idx = mPrimaryKeyAttrs[i]; + const QgsField &fld = field( idx ); + + whereClause += delim + QString( "%3%1=?" ).arg( mConnection->fieldExpression( fld ) ); + delim = " AND "; + } + } + break; + + case pktRowId: + return "ROWID=?"; + break; + + case pktUnknown: + Q_ASSERT( !"FAILURE: Primary key unknown" ); + whereClause = "NULL IS NOT NULL"; + break; + } + + if ( !mSqlWhereClause.isEmpty() ) + { + if ( !whereClause.isEmpty() ) + whereClause += " AND "; + + whereClause += "(" + mSqlWhereClause + ")"; + } + + return whereClause; +} + +void QgsOracleProvider::appendPkParams( QgsFeatureId fid, QSqlQuery &qry ) const +{ + switch ( mPrimaryKeyType ) + { + case pktInt: + QgsDebugMsgLevel( QString( "addBindValue pk %1" ).arg( FID_TO_STRING( fid ) ), 4 ); + qry.addBindValue( FID_TO_STRING( fid ) ); + break; + + case pktRowId: + case pktFidMap: + { + QMap::const_iterator it = mFidToKey.find( fid ); + if ( it != mFidToKey.constEnd() ) + { + foreach ( const QVariant &v, it.value().toList() ) + { + QgsDebugMsgLevel( QString( "addBindValue pk %1" ).arg( FID_TO_STRING( fid ) ), 4 ); + qry.addBindValue( v ); + } + } + else + { + QgsDebugMsg( QString( "key values for fid %1 not found. " ).arg( fid ) ); + for ( int i = 0; i < mPrimaryKeyAttrs.size(); i++ ) + { + QgsDebugMsgLevel( QString( "addBindValue pk NULL" ).arg( fid ), 4 ); + qry.addBindValue( QVariant() ); + } + } + break; + } + break; + + case pktUnknown: + QgsDebugMsg( "Unknown key type" ); + break; + } +} + +QString QgsOracleProvider::whereClause( QgsFeatureId featureId ) const +{ + QString whereClause; + + switch ( mPrimaryKeyType ) + { + case pktInt: + Q_ASSERT( mPrimaryKeyAttrs.size() == 1 ); + whereClause = QString( "%1=%2" ).arg( quotedIdentifier( field( mPrimaryKeyAttrs[0] ).name() ) ).arg( featureId ); + break; + + case pktRowId: + case pktFidMap: + { + QMap::const_iterator it = mFidToKey.find( featureId ); + if ( it != mFidToKey.constEnd() ) + { + QList pkVals = it.value().toList(); + + if ( mPrimaryKeyType == pktFidMap ) + { + Q_ASSERT( pkVals.size() == mPrimaryKeyAttrs.size() ); + + QString delim = ""; + for ( int i = 0; i < mPrimaryKeyAttrs.size(); i++ ) + { + int idx = mPrimaryKeyAttrs[i]; + const QgsField &fld = field( idx ); + + whereClause += delim + QString( "%1=%2" ).arg( mConnection->fieldExpression( fld ) ).arg( quotedValue( pkVals[i].toString() ) ); + delim = " AND "; + } + } + else + { + whereClause += QString( "ROWID=%1" ).arg( quotedValue( pkVals[0].toString() ) ); + } + } + else + { + QgsDebugMsg( QString( "FAILURE: Key values for feature %1 not found." ).arg( featureId ) ); + whereClause = "NULL IS NOT NULL"; + } + } + break; + + case pktUnknown: + Q_ASSERT( !"FAILURE: Primary key unknown" ); + whereClause = "NULL IS NOT NULL"; + break; + } + + if ( !mSqlWhereClause.isEmpty() ) + { + if ( !whereClause.isEmpty() ) + whereClause += " AND "; + + whereClause += "(" + mSqlWhereClause + ")"; + } + + return whereClause; +} + +bool QgsOracleProvider::featureAtId( QgsFeatureId featureId, QgsFeature& feature, bool fetchGeometry, QgsAttributeList fetchAttributes ) +{ + feature.setValid( false ); + + if ( !openQuery( "featureAtId", fetchAttributes, fetchGeometry, whereClause( featureId ) ) ) + return false; + + if ( !mQry.next() ) + { + QgsMessageLog::logMessage( tr( "feature %1 not found" ).arg( featureId ), tr( "Oracle" ) ); + return false; + } + + if ( !getFeature( fetchGeometry, feature, fetchAttributes ) ) + { + return false; + } + + if ( mQry.next() ) + { + QgsMessageLog::logMessage( tr( "found more features instead of just one." ), tr( "Oracle" ) ); + } + + feature.setValid( true ); + return true; +} + +void QgsOracleProvider::setExtent( QgsRectangle& newExtent ) +{ + mLayerExtent.setXMaximum( newExtent.xMaximum() ); + mLayerExtent.setXMinimum( newExtent.xMinimum() ); + mLayerExtent.setYMaximum( newExtent.yMaximum() ); + mLayerExtent.setYMinimum( newExtent.yMinimum() ); +} + +/** + * Return the feature type + */ +QGis::WkbType QgsOracleProvider::geometryType() const +{ + return mRequestedGeomType != QGis::WKBUnknown ? mRequestedGeomType : mDetectedGeomType; +} + +const QgsField &QgsOracleProvider::field( int index ) const +{ + QgsFieldMap::const_iterator it = mAttributeFields.find( index ); + + if ( it == mAttributeFields.constEnd() ) + { + QgsMessageLog::logMessage( tr( "FAILURE: Field %1 not found." ).arg( index ), tr( "Oracle" ) ); + throw OracleFieldNotFound(); + } + + return it.value(); +} + +/** + * Return the number of fields + */ +uint QgsOracleProvider::fieldCount() const +{ + return mAttributeFields.size(); +} + +const QgsFieldMap & QgsOracleProvider::fields() const +{ + return mAttributeFields; +} + +QString QgsOracleProvider::dataComment() const +{ + return mDataComment; +} + +void QgsOracleProvider::rewind() +{ + if ( mQry.isActive() ) + { + mQry.first(); + } + loadFields(); +} + +/** @todo XXX Perhaps this should be promoted to QgsDataProvider? */ +QString QgsOracleProvider::endianString() +{ + switch ( QgsApplication::endian() ) + { + case QgsApplication::NDR: + return QString( "NDR" ); + break; + case QgsApplication::XDR: + return QString( "XDR" ); + break; + default : + return QString( "Unknown" ); + } +} + +bool QgsOracleProvider::loadFields() +{ + mAttributeFields.clear(); + mDefaultValues.clear(); + + QSqlQuery qry( *mConnection ); + + QMap comments; + QMap types; + QMap defvalues; + + if ( !mIsQuery ) + { + QgsDebugMsg( QString( "Loading fields for table %1" ).arg( mTableName ) ); + + if ( exec( qry, QString( "SELECT comments FROM all_tab_comments WHERE owner=%1 AND table_name=%2" ) + .arg( quotedValue( mOwnerName ) ) + .arg( quotedValue( mTableName ) ) ) ) + { + if ( qry.next() ) + mDataComment = qry.value( 0 ).toString(); + } + else + { + QgsMessageLog::logMessage( tr( "Loading comment for table %1.%2 failed [%3]" ) + .arg( mOwnerName ) + .arg( mTableName ) + .arg( qry.lastError().text() ), + tr( "Oracle" ) ); + } + + qry.finish(); + + if ( exec( qry, QString( "SELECT column_name,comments FROM all_col_comments t WHERE t.owner=%1 AND t.table_name=%2 AND t.column_name<>%3" ) + .arg( quotedValue( mOwnerName ) ) + .arg( quotedValue( mTableName ) ) + .arg( quotedValue( mGeometryColumn ) ) ) ) + { + while ( qry.next() ) + { + comments.insert( qry.value( 0 ).toString(), qry.value( 1 ).toString() ); + } + } + else + { + QgsMessageLog::logMessage( tr( "Loading comment for columns of table %1.%2 failed [%3]" ).arg( mOwnerName ).arg( mTableName ).arg( qry.lastError().text() ), tr( "Oracle" ) ); + } + + qry.finish(); + + if ( exec( qry, QString( "SELECT" + " t.column_name" + ",CASE WHEN t.data_type_owner IS NULL THEN t.data_type ELSE t.data_type_owner||'.'||t.data_type END" + ",t.data_precision" + ",t.data_scale" + ",t.char_length" + ",t.char_used" + ",t.data_default" + " FROM all_tab_columns t" + " WHERE t.owner=%1 AND t.table_name=%2 AND t.column_name<>%3" + " ORDER BY t.column_id" ) + .arg( quotedValue( mOwnerName ) ) + .arg( quotedValue( mTableName ) ) + .arg( quotedValue( mGeometryColumn ) ) + ) ) + { + while ( qry.next() ) + { + QString name = qry.value( 0 ).toString(); + QString type = qry.value( 1 ).toString(); + int prec = qry.value( 2 ).toInt(); + int scale = qry.value( 3 ).toInt(); + int clength = qry.value( 4 ).toInt(); + bool cused = qry.value( 5 ).toString() == "C"; + QVariant defValue = qry.value( 6 ); + + if ( type == "CHAR" || type == "VARCHAR2" || type == "VARCHAR" ) + { + types.insert( name, QString( "%1(%2 %3)" ).arg( type ).arg( clength ).arg( cused ? "CHAR" : "BYTE" ) ); + } + else if ( type == "NCHAR" || type == "NVARCHAR2" || type == "NVARCHAR" ) + { + types.insert( name, QString( "%1(%2)" ).arg( type ).arg( clength ) ); + } + else if ( type == "NUMBER" ) + { + if ( scale == 0 ) + { + types.insert( name, QString( "%1(%2)" ).arg( type ).arg( prec ) ); + } + else + { + types.insert( name, QString( "%1(%2,%3)" ).arg( type ).arg( prec ).arg( scale ) ); + } + } + else if ( type == "MDSYS.SDO_GEOMETRY" ) + { + if ( !mConnection->hasSpatial() ) + { + QgsMessageLog::logMessage( tr( "Other spatial field %1.%2.%3 ignored" ).arg( mOwnerName ).arg( mTableName ).arg( name ), tr( "Oracle" ) ); + continue; + } + } + else + { + types.insert( name, type ); + } + + defvalues.insert( name, defValue ); + } + } + else + { + QgsMessageLog::logMessage( tr( "Loading field types for table %1.%2 failed [%3]" ) + .arg( mOwnerName ) + .arg( mTableName ) + .arg( qry.lastError().text() ), + tr( "Oracle" ) ); + } + + if ( exec( qry, QString( "SELECT i.index_name,i.domidx_opstatus" + " FROM all_indexes i" + " JOIN all_ind_columns c ON i.owner=c.index_owner AND i.index_name=c.index_name AND c.column_name=%3" + " WHERE i.table_owner=%1 AND i.table_name=%2 AND i.ityp_owner='MDSYS' AND i.ityp_name='SPATIAL_INDEX'" ) + .arg( quotedValue( mOwnerName ) ) + .arg( quotedValue( mTableName ) ) + .arg( quotedValue( mGeometryColumn ) ) ) ) + { + if ( qry.next() ) + { + mSpatialIndex = qry.value( 0 ).toString(); + if ( qry.value( 1 ).toString() != "VALID" ) + { + QgsMessageLog::logMessage( tr( "Invalid spatial index %1 on column %2.%3.%4 found - expect poor performance." ) + .arg( mSpatialIndex ) + .arg( mOwnerName ) + .arg( mTableName ) + .arg( mGeometryColumn ), + tr( "Oracle" ) ); + mSpatialIndex = QString::null; + } + else + { + QgsDebugMsg( QString( "Valid spatial index %1 found" ).arg( mSpatialIndex ) ); + } + } + else + { + QgsMessageLog::logMessage( tr( "No spatial index on column %1.%2.%3 found - expect poor performance." ) + .arg( mOwnerName ) + .arg( mTableName ) + .arg( mGeometryColumn ), + tr( "Oracle" ) ); + } + } + else + { + QgsMessageLog::logMessage( tr( "Probing for spatial index on column %1.%2.%3 failed [%4]" ) + .arg( mOwnerName ) + .arg( mTableName ) + .arg( mGeometryColumn ) + .arg( qry.lastError().text() ), + tr( "Oracle" ) ); + } + + qry.finish(); + + mEnabledCapabilities |= QgsVectorDataProvider::CreateSpatialIndex; + } + + if ( !exec( qry, QString( "SELECT * FROM %1 WHERE rownum=0" ).arg( mQuery ) ) ) + { + QgsMessageLog::logMessage( tr( "Retrieving fields from '%1' failed [%2]" ).arg( mQuery ).arg( qry.lastError().text() ), tr( "Oracle" ) ); + return false; + } + + QSqlRecord record = qry.record(); + + for ( int i = 0; i < record.count(); i++ ) + { + QSqlField field = record.field( i ); + + if ( field.name() == mGeometryColumn ) + continue; + + mAttributeFields.insert( i, QgsField( field.name(), field.type(), types.value( field.name() ), field.length(), field.precision(), comments.value( field.name() ) ) ); + mDefaultValues.insert( i, defvalues.value( field.name(), QVariant() ) ); + } + + return true; +} + +bool QgsOracleProvider::hasSufficientPermsAndCapabilities() +{ + QgsDebugMsg( "Checking for permissions on the relation" ); + + mEnabledCapabilities = QgsVectorDataProvider::SelectAtId | QgsVectorDataProvider::SelectGeometryAtId; + + QSqlQuery qry( *mConnection ); + if ( !mIsQuery ) + { + if ( mConnection->currentUser() == mOwnerName ) + { + // full set of privileges for the owner + mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures + | QgsVectorDataProvider::ChangeAttributeValues + | QgsVectorDataProvider::AddFeatures + | QgsVectorDataProvider::AddAttributes + | QgsVectorDataProvider::DeleteAttributes + | QgsVectorDataProvider::ChangeGeometries + ; + } + else if ( exec( qry, QString( "SELECT privilege FROM all_tab_privs WHERE table_schema=%1 AND table_name=%2 AND privilege IN ('DELETE','UPDATE','INSERT','ALTER TABLE')" ) + .arg( quotedValue( mOwnerName ) ).arg( quotedValue( mTableName ) ) ) ) + { + // check grants + while ( qry.next() ) + { + QString priv = qry.value( 0 ).toString(); + + if ( priv == "DELETE" ) + { + mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures; + } + else if ( priv == "UPDATE" ) + { + mEnabledCapabilities |= QgsVectorDataProvider::ChangeAttributeValues; + } + else if ( priv == "INSERT" ) + { + mEnabledCapabilities |= QgsVectorDataProvider::AddFeatures; + } + else if ( priv == "ALTER TABLE" ) + { + mEnabledCapabilities |= QgsVectorDataProvider::AddAttributes | QgsVectorDataProvider::DeleteAttributes; + } + } + + if ( !mGeometryColumn.isNull() ) + { + if ( exec( qry, QString( "SELECT 1 FROM all_col_privs WHERE table_schema=%1 AND table_name=%2 AND column_name=%3 AND privilege='UPDATE'" ) + .arg( quotedValue( mOwnerName ) ) + .arg( quotedValue( mTableName ) ) + .arg( quotedValue( mGeometryColumn ) ) ) ) + { + if ( qry.next() ) + mEnabledCapabilities |= QgsVectorDataProvider::ChangeGeometries; + } + else + { + QgsMessageLog::logMessage( tr( "Unable to determine geometry column access privileges for column %1.%2.\nThe error message from the database was:\n%3.\nSQL: %4" ) + .arg( mQuery ) + .arg( mGeometryColumn ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), + tr( "Oracle" ) ); + } + } + } + else + { + QgsMessageLog::logMessage( tr( "Unable to determine table access privileges for the table %1.\nThe error message from the database was:\n%2.\nSQL: %3" ) + .arg( mQuery ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), + tr( "Oracle" ) ); + } + + } + else + { + // Check if the sql is a select query + if ( !mQuery.startsWith( "(" ) && !mQuery.endsWith( ")" ) ) + { + QgsMessageLog::logMessage( tr( "The custom query is not a select query." ), tr( "Oracle" ) ); + return false; + } + + // get a new alias for the subquery + int index = 0; + QString alias; + QRegExp regex; + do + { + alias = QString( "subQuery_%1" ).arg( QString::number( index++ ) ); + QString pattern = QString( "(\\\"?)%1\\1" ).arg( QRegExp::escape( alias ) ); + regex.setPattern( pattern ); + regex.setCaseSensitivity( Qt::CaseInsensitive ); + } + while ( mQuery.contains( regex ) ); + + // convert the custom query into a subquery + mQuery = QString( "%1 AS %2" ) + .arg( mQuery ) + .arg( quotedIdentifier( alias ) ); + + if ( !exec( qry, QString( "SELECT * FROM %1 WHERE rownum=0" ).arg( mQuery ) ) ) + { + QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + return false; + } + } + + qry.finish(); + + return true; +} + +bool QgsOracleProvider::determinePrimaryKey() +{ + if ( !loadFields() ) + { + return false; + } + + // check to see if there is an unique index on the relation, which + // can be used as a key into the table. Primary keys are always + // unique indices, so we catch them as well. + + QSqlQuery qry( *mConnection ); + if ( !mIsQuery ) + { + + if ( !exec( qry, QString( "SELECT column_name" + " FROM all_ind_columns a" + " JOIN all_constraints b ON a.index_name=constraint_name AND a.index_owner=b.owner" + " WHERE b.constraint_type='P' AND b.owner=%1 AND b.table_name=%2" ) + .arg( quotedValue( mOwnerName ) ).arg( quotedValue( mTableName ) ) ) ) + { + QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + return false; + } + + bool isInt = true; + + while ( qry.next() ) + { + QString name = qry.value( 0 ).toString(); + + QgsFieldMap::const_iterator it = mAttributeFields.begin(); + while ( it != mAttributeFields.end() && it->name() != name ) + it++; + + if ( it == mAttributeFields.end() ) + { + QgsMessageLog::logMessage( tr( "Primary key field %1 not found in %2" ).arg( name ).arg( mQuery ), tr( "Oracle" ) ); + return false; + } + + if ( isInt && + it->type() != QVariant::Int && + it->type() != QVariant::LongLong ) + isInt = false; + + mPrimaryKeyAttrs << it.key(); + } + + if ( mPrimaryKeyAttrs.size() > 0 ) + { + mPrimaryKeyType = ( mPrimaryKeyAttrs.size() == 1 && isInt ) ? pktInt : pktFidMap; + } + else if ( !exec( qry, QString( "SELECT 1 FROM all_tables WHERE owner=%1 AND table_name=%2" ).arg( quotedValue( mOwnerName ) ).arg( quotedValue( mTableName ) ) ) ) + { + QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + } + else if ( qry.next() ) + { + // is table + mPrimaryKeyType = pktRowId; + } + else + { + QString primaryKey = mUri.keyColumn(); + mPrimaryKeyType = pktUnknown; + + if ( !primaryKey.isEmpty() ) + { + int idx = fieldNameIndex( primaryKey ); + + if ( idx >= 0 ) + { + if ( mAttributeFields[idx].type() == QVariant::Int || mAttributeFields[idx].type() == QVariant::LongLong ) + { + if ( mUseEstimatedMetadata || uniqueData( mQuery, primaryKey ) ) + { + mPrimaryKeyType = pktInt; + mPrimaryKeyAttrs << idx; + } + else + { + QgsMessageLog::logMessage( tr( "Primary key field '%1' for view not unique." ).arg( primaryKey ), tr( "Oracle" ) ); + } + } + else + { + QgsMessageLog::logMessage( tr( "Type '%1' of primary key field '%2' for view invalid." ).arg( mAttributeFields[idx].typeName() ).arg( primaryKey ), tr( "Oracle" ) ); + } + } + else + { + QgsMessageLog::logMessage( tr( "Key field '%1' for view not found." ).arg( primaryKey ), tr( "Oracle" ) ); + } + } + else + { + QgsMessageLog::logMessage( tr( "No key field for view given." ), tr( "Oracle" ) ); + } + } + } + else + { + QString primaryKey = mUri.keyColumn(); + int idx = fieldNameIndex( mUri.keyColumn() ); + + if ( idx >= 0 && ( mAttributeFields[idx].type() == QVariant::Int || mAttributeFields[idx].type() == QVariant::LongLong ) ) + { + if ( mUseEstimatedMetadata || uniqueData( mQuery, primaryKey ) ) + { + mPrimaryKeyType = pktInt; + mPrimaryKeyAttrs << idx; + } + } + else + { + QgsMessageLog::logMessage( tr( "No key field for query given." ), tr( "Oracle" ) ); + mPrimaryKeyType = pktUnknown; + } + } + + qry.finish(); + + mValid = mPrimaryKeyType != pktUnknown; + + return mValid; +} + +bool QgsOracleProvider::uniqueData( QString query, QString colName ) +{ + Q_UNUSED( query ); + // Check to see if the given column contains unique data + QSqlQuery qry( *mConnection ); + + QString table = mQuery; + if ( !mSqlWhereClause.isEmpty() ) + { + table += " WHERE " + mSqlWhereClause; + } + + QString sql = QString( "SELECT (SELECT count(distinct %1) FROM %2)-(SELECT count(%1) FROM %2) FROM dual" ) + .arg( quotedIdentifier( colName ) ) + .arg( mQuery ); + + if ( !exec( qry, sql ) || !qry.next() ) + { + QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + return false; + } + + return qry.value( 0 ).toInt() == 0; +} + +// Returns the minimum value of an attribute +QVariant QgsOracleProvider::minimumValue( int index ) +{ + try + { + // get the field name + const QgsField &fld = field( index ); + QString sql = QString( "SELECT min(%1) FROM %2" ) + .arg( quotedIdentifier( fld.name() ) ) + .arg( mQuery ); + + if ( !mSqlWhereClause.isEmpty() ) + { + sql += QString( " WHERE %1" ).arg( mSqlWhereClause ); + } + + QSqlQuery qry( *mConnection ); + + if ( !exec( qry, sql ) ) + { + QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + return QVariant( QString::null ); + } + + if ( qry.next() ) + { + return qry.value( 0 ); + } + } + catch ( OracleFieldNotFound ) + { + ; + } + return QVariant( QString::null ); +} + +// Returns the list of unique values of an attribute +void QgsOracleProvider::uniqueValues( int index, QList &uniqueValues, int limit ) +{ + uniqueValues.clear(); + + try + { + // get the field name + const QgsField &fld = field( index ); + QString sql = QString( "SELECT DISTINCT %1 FROM %2" ) + .arg( quotedIdentifier( fld.name() ) ) + .arg( mQuery ); + + if ( !mSqlWhereClause.isEmpty() ) + { + sql += QString( " WHERE %1" ).arg( mSqlWhereClause ); + } + + sql += QString( " ORDER BY %1" ) + .arg( quotedIdentifier( fld.name() ) ); + + if ( limit >= 0 ) + { + sql = QString( "SELECT * FROM (%1) WHERE rownum<=%2" ).arg( sql ).arg( limit ); + } + + QSqlQuery qry( *mConnection ); + + if ( !exec( qry, sql ) ) + { + QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + return; + } + + while ( qry.next() ) + { + uniqueValues.append( qry.value( 0 ) ); + } + } + catch ( OracleFieldNotFound ) + { + } +} + +// Returns the maximum value of an attribute +QVariant QgsOracleProvider::maximumValue( int index ) +{ + try + { + // get the field name + const QgsField &fld = field( index ); + QString sql = QString( "SELECT max(%1) FROM %2" ) + .arg( quotedIdentifier( fld.name() ) ) + .arg( mQuery ); + + if ( !mSqlWhereClause.isEmpty() ) + { + sql += QString( " WHERE %1" ).arg( mSqlWhereClause ); + } + + QSqlQuery qry( *mConnection ); + + if ( !exec( qry, sql ) ) + { + QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + return QVariant( QString::null ); + } + + if ( qry.next() ) + { + return qry.value( 0 ); + } + } + catch ( OracleFieldNotFound ) + { + ; + } + + return QVariant( QString::null ); +} + + +bool QgsOracleProvider::isValid() +{ + return mValid; +} + +QVariant QgsOracleProvider::defaultValue( int fieldId ) +{ + return mDefaultValues.value( fieldId, QVariant() ); +} + +QString QgsOracleProvider::paramValue( QString fieldValue, const QString &defaultValue ) const +{ + if ( fieldValue.isNull() ) + { + return QString::null; + } + else if ( fieldValue == defaultValue && !defaultValue.isNull() ) + { + QSqlQuery qry( *mConnection ); + if ( !exec( qry, QString( "SELECT %1" ).arg( defaultValue ) ) || !qry.next() ) + { + throw OracleException( qry ); + } + + return qry.value( 0 ).toString(); + } + else + { + return fieldValue; + } +} + + +bool QgsOracleProvider::addFeatures( QgsFeatureList &flist ) +{ + if ( flist.size() == 0 ) + return true; + + if ( mIsQuery ) + return false; + + bool returnvalue = true; + + + QSqlDatabase db( *mConnection ); + + try + { + QSqlQuery qry( db ); + + if ( !db.transaction() ) + { + QgsMessageLog::logMessage( tr( "Could not start transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + + // Prepare the INSERT statement + QString insert = QString( "INSERT INTO %1(" ).arg( mQuery ); + QString values = ") VALUES ("; + QString delim = ""; + + QStringList defaultValues; + QList fieldId; + + if ( !mGeometryColumn.isNull() ) + { + insert += quotedIdentifier( mGeometryColumn ); + values += "?"; + delim = ","; + } + + if ( mPrimaryKeyType == pktInt || mPrimaryKeyType == pktFidMap ) + { + foreach ( int idx, mPrimaryKeyAttrs ) + { + insert += delim + quotedIdentifier( field( idx ).name() ); + values += delim + "?"; + delim = ","; + fieldId << idx; + defaultValues << defaultValue( idx ).toString(); + } + } + + const QgsAttributeMap &attributevec = flist[0].attributeMap(); + + // look for unique attribute values to place in statement instead of passing as parameter + // e.g. for defaults + for ( QgsAttributeMap::const_iterator it = attributevec.begin(); it != attributevec.end(); it++ ) + { + if ( fieldId.contains( it.key() ) ) + continue; + + QgsFieldMap::const_iterator fit = mAttributeFields.find( it.key() ); + if ( fit == mAttributeFields.end() ) + continue; + + QString fieldname = fit->name(); + + QgsDebugMsg( "Checking field against: " + fieldname ); + + if ( fieldname.isEmpty() || fieldname == mGeometryColumn ) + continue; + + int i; + for ( i = 1; i < flist.size(); i++ ) + { + const QgsAttributeMap &attributevec = flist[i].attributeMap(); + + QgsAttributeMap::const_iterator thisit = attributevec.find( it.key() ); + if ( thisit == attributevec.end() ) + break; + + if ( *thisit != *it ) + break; + } + + insert += delim + quotedIdentifier( fieldname ); + + QString defVal = defaultValue( it.key() ).toString(); + + if ( i == flist.size() ) + { + if ( *it == defVal ) + { + if ( defVal.isNull() ) + { + values += delim + "NULL"; + } + else + { + values += delim + defVal; + } + } + else if ( fit->typeName() == "MDSYS.SDO_GEOMETRY" ) + { + values += delim + "?"; + } + else + { + values += delim + quotedValue( it->toString() ); + } + } + else + { + // value is not unique => add parameter + values += delim + "?"; + defaultValues.append( defVal ); + fieldId.append( it.key() ); + } + + delim = ","; + } + + insert += values + ")"; + + QgsDebugMsgLevel( QString( "SQL prepare: %1" ).arg( insert ), 4 ); + if ( !qry.prepare( insert ) ) + { + QgsMessageLog::logMessage( tr( "Could not prepare insert statement.\nSQL: %1\nERROR: %2" ) + .arg( qry.lastQuery() ) + .arg( qry.lastError().text() ), + tr( "Oracle" ) ); + throw OracleException( qry ); + } + + for ( QgsFeatureList::iterator features = flist.begin(); features != flist.end(); features++ ) + { + const QgsAttributeMap &attributevec = features->attributeMap(); + + QgsDebugMsg( QString( "insert feature %1" ).arg( features->id() ) ); + + if ( !mGeometryColumn.isNull() ) + { + appendGeomParam( features->geometry(), qry ); + } + + for ( int i = 0; i < fieldId.size(); i++ ) + { + QgsAttributeMap::const_iterator attr = attributevec.find( fieldId[i] ); + + QString v; + if ( attr == attributevec.end() ) + { + const QgsField &fld = field( fieldId[i] ); + v = paramValue( defaultValues[i], defaultValues[i] ); + features->addAttribute( fieldId[i], convertValue( fld.type(), v ) ); + } + else + { + v = paramValue( attr.value().toString(), defaultValues[i] ); + + if ( v != attr.value().toString() ) + { + const QgsField &fld = field( fieldId[i] ); + features->changeAttribute( fieldId[i], convertValue( fld.type(), v ) ); + } + } + + QgsDebugMsgLevel( QString( "addBindValue: %1" ).arg( v ), 4 ); + qry.addBindValue( v ); + } + + if ( !qry.exec() ) + throw OracleException( qry ); + + if ( mPrimaryKeyType == pktRowId ) + { + features->setFeatureId( lookupFid( QList() << QVariant( qry.lastInsertId() ) ) ); + QgsDebugMsgLevel( QString( "new fid=%1" ).arg( features->id() ), 4 ); + } + } + + qry.finish(); + + if ( !db.commit() ) + { + QgsMessageLog::logMessage( tr( "Could not commit transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + + // update feature ids + if ( mPrimaryKeyType == pktInt || mPrimaryKeyType == pktFidMap ) + { + for ( QgsFeatureList::iterator features = flist.begin(); features != flist.end(); features++ ) + { + const QgsAttributeMap &attributevec = features->attributeMap(); + + if ( mPrimaryKeyType == pktInt ) + { + features->setFeatureId( STRING_TO_FID( attributevec[ mPrimaryKeyAttrs[0] ] ) ); + } + else + { + QList primaryKeyVals; + + foreach ( int idx, mPrimaryKeyAttrs ) + { + primaryKeyVals << attributevec[ idx ]; + } + + features->setFeatureId( lookupFid( QVariant( primaryKeyVals ) ) ); + } + QgsDebugMsgLevel( QString( "new fid=%1" ).arg( features->id() ), 4 ); + } + } + + mFeaturesCounted += flist.size(); + } + catch ( OracleException &e ) + { + QgsDebugMsg( QString( "Oracle error: %1" ).arg( e.errorMessage() ) ); + pushError( tr( "Oracle error while adding features: %1" ).arg( e.errorMessage() ) ); + if ( !db.rollback() ) + { + QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) ); + } + returnvalue = false; + } + + rewind(); + return returnvalue; +} + +bool QgsOracleProvider::deleteFeatures( const QgsFeatureIds & id ) +{ + bool returnvalue = true; + + if ( mIsQuery ) + return false; + + QSqlDatabase db( *mConnection ); + + try + { + QSqlQuery qry( db ); + + if ( !db.transaction() ) + { + QgsMessageLog::logMessage( tr( "Could not start transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + + for ( QgsFeatureIds::const_iterator it = id.begin(); it != id.end(); ++it ) + { + QString sql = QString( "DELETE FROM %1 WHERE %2" ) + .arg( mQuery ).arg( whereClause( *it ) ); + QgsDebugMsg( "delete sql: " + sql ); + + //send DELETE statement and do error handling + if ( !exec( qry, sql ) ) + throw OracleException( qry ); + + QVariant v = mFidToKey[ *it ]; + mFidToKey.remove( *it ); + mKeyToFid.remove( v ); + } + + qry.finish(); + + if ( !db.commit() ) + { + QgsMessageLog::logMessage( tr( "Could not commit transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + + mFeaturesCounted -= id.size(); + } + catch ( OracleException &e ) + { + pushError( tr( "Oracle error while deleting features: %1" ).arg( e.errorMessage() ) ); + if ( !db.rollback() ) + QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) ); + returnvalue = false; + } + rewind(); + return returnvalue; +} + +bool QgsOracleProvider::addAttributes( const QList &attributes ) +{ + bool returnvalue = true; + + if ( mIsQuery ) + return false; + + QSqlDatabase db( *mConnection ); + + try + { + QSqlQuery qry( db ); + + if ( !db.transaction() ) + { + QgsMessageLog::logMessage( tr( "Could not start transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + + for ( QList::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter ) + { + QString type = iter->typeName(); + if ( type == "char" || type == "varchar2" ) + { + type = QString( "%1(%2 char)" ).arg( type ).arg( iter->length() ); + } + else if ( type == "number" ) + { + if ( iter->precision() > 0 ) + { + type = QString( "%1(%2,%3)" ).arg( type ).arg( iter->length() ).arg( iter->precision() ); + } + else + { + type = QString( "%1(%2,%3)" ).arg( type ).arg( iter->length() ); + } + } + + QString sql = QString( "ALTER TABLE %1 ADD %2 %3" ) + .arg( mQuery ) + .arg( quotedIdentifier( iter->name() ) ) + .arg( type ); + QgsDebugMsg( sql ); + + if ( !exec( qry, sql ) ) + throw OracleException( qry ); + + if ( !iter->comment().isEmpty() ) + { + sql = QString( "COMMENT ON COLUMN %1.%2 IS %3" ) + .arg( mQuery ) + .arg( quotedIdentifier( iter->name() ) ) + .arg( quotedValue( iter->comment() ) ); + if ( !exec( qry, sql ) ) + throw OracleException( qry ); + } + + qry.finish(); + } + + if ( !db.commit() ) + { + QgsMessageLog::logMessage( tr( "Could not commit transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + } + catch ( OracleException &e ) + { + pushError( tr( "Oracle error while adding attributes: %1" ).arg( e.errorMessage() ) ); + if ( !db.rollback() ) + QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) ); + returnvalue = false; + } + + rewind(); + return returnvalue; +} + +bool QgsOracleProvider::deleteAttributes( const QgsAttributeIds& ids ) +{ + bool returnvalue = true; + + if ( mIsQuery ) + return false; + + QSqlDatabase db( *mConnection ); + + try + { + QSqlQuery qry( db ); + + if ( !db.transaction() ) + { + QgsMessageLog::logMessage( tr( "Could not start transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + + qry.finish(); + + for ( QgsAttributeIds::const_iterator iter = ids.begin(); iter != ids.end(); ++iter ) + { + QgsFieldMap::const_iterator field_it = mAttributeFields.find( *iter ); + if ( field_it == mAttributeFields.constEnd() ) + continue; + + QString sql = QString( "ALTER TABLE %1 DROP COLUMN %2" ) + .arg( mQuery ) + .arg( quotedIdentifier( field_it->name() ) ); + + //send sql statement and do error handling + if ( !exec( qry, sql ) ) + throw OracleException( qry ); + + //delete the attribute from mAttributeFields + mAttributeFields.remove( *iter ); + mDefaultValues.remove( *iter ); + } + + if ( !db.commit() ) + { + QgsMessageLog::logMessage( tr( "Could not commit transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + } + catch ( OracleException &e ) + { + pushError( tr( "Oracle error while deleting attributes: %1" ).arg( e.errorMessage() ) ); + if ( !db.rollback() ) + { + QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) ); + } + returnvalue = false; + } + + rewind(); + return returnvalue; +} + +bool QgsOracleProvider::changeAttributeValues( const QgsChangedAttributesMap & attr_map ) +{ + bool returnvalue = true; + + if ( mIsQuery ) + return false; + + QSqlDatabase db( *mConnection ); + + try + { + QSqlQuery qry( db ); + + if ( !db.transaction() ) + { + QgsMessageLog::logMessage( tr( "Could not start transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + + // cycle through the features + for ( QgsChangedAttributesMap::const_iterator iter = attr_map.begin(); iter != attr_map.end(); ++iter ) + { + QgsFeatureId fid = iter.key(); + + // skip added features + if ( FID_IS_NEW( fid ) ) + continue; + + QString sql = QString( "UPDATE %1 SET " ).arg( mQuery ); + + const QgsAttributeMap& attrs = iter.value(); + bool pkChanged = false; + + // cycle through the changed attributes of the feature + QString delim; + for ( QgsAttributeMap::const_iterator siter = attrs.begin(); siter != attrs.end(); ++siter ) + { + try + { + QgsField fld = field( siter.key() ); + + pkChanged = pkChanged || mPrimaryKeyAttrs.contains( siter.key() ); + + sql += delim + QString( "%1=" ).arg( quotedIdentifier( fld.name() ) ); + delim = ","; + + if ( fld.typeName() == "MDSYS.SDO_GEOMETRY" ) + { + sql += QString( "SDO_UTIL.FROM_WKTGEOMETRY(%1)" ).arg( quotedValue( siter->toString() ) ); + } + else + { + sql += quotedValue( siter->toString() ); + } + } + catch ( OracleFieldNotFound ) + { + // Field was missing - shouldn't happen + } + } + + sql += QString( " WHERE %1" ).arg( whereClause( fid ) ); + + if ( !exec( qry, sql ) ) + throw OracleException( qry ); + + // update feature id map if key was changed + if ( pkChanged && mPrimaryKeyType == pktFidMap ) + { + QVariant v = mFidToKey[ fid ]; + mFidToKey.remove( fid ); + mKeyToFid.remove( v ); + + QList k = v.toList(); + + for ( int i = 0; i < mPrimaryKeyAttrs.size(); i++ ) + { + int idx = mPrimaryKeyAttrs[i]; + if ( !attrs.contains( idx ) ) + continue; + + k[i] = attrs[ idx ]; + } + + mFidToKey.insert( fid, k ); + mKeyToFid.insert( k, fid ); + } + } + + if ( !db.commit() ) + { + QgsMessageLog::logMessage( tr( "Could not commit transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + } + catch ( OracleException &e ) + { + pushError( tr( "Oracle error while changing attributes: %1" ).arg( e.errorMessage() ) ); + if ( !db.rollback() ) + { + QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) ); + } + returnvalue = false; + } + + rewind(); + + return returnvalue; +} + +void QgsOracleProvider::appendGeomParam( QgsGeometry *geom, QSqlQuery &qry ) const +{ + QOCISpatialGeometry g; + + wkbPtr ptr; + ptr.ucPtr = geom ? geom->asWkb() : 0; + g.isNull = !ptr.ucPtr; + g.gtype = -1; + g.srid = mSrid < 1 ? -1 : mSrid; + + if ( !g.isNull ) + { + ptr.ucPtr++; // skip endianess + + g.eleminfo.clear(); + g.ordinates.clear(); + + QString expr; + int iOrdinate = 1; + QGis::WkbType type = ( QGis::WkbType ) * ptr.iPtr++; + int dim = 2; + + switch ( type ) + { + case QGis::WKBPoint25D: + dim = 3; + case QGis::WKBPoint: + g.srid = mSrid; + g.gtype = SDO_GTYPE( dim, gtPoint ); + g.x = *ptr.dPtr++; + g.y = *ptr.dPtr++; + g.z = dim == 3 ? *ptr.dPtr++ : 0.0; + break; + + case QGis::WKBLineString25D: + case QGis::WKBMultiLineString25D: + dim = 3; + case QGis::WKBLineString: + case QGis::WKBMultiLineString: + { + g.gtype = SDO_GTYPE( dim, gtLine ); + int nLines = 1; + if ( type == QGis::WKBMultiLineString25D || type == QGis::WKBMultiLineString ) + { + g.gtype = SDO_GTYPE( dim, gtMultiLine ); + nLines = *ptr.iPtr++; + ptr.ucPtr++; // Skip endianess of first linestring + ptr.iPtr++; // Skip type of first linestring + } + + for ( int iLine = 0; iLine < nLines; iLine++ ) + { + g.eleminfo << iOrdinate << 2 << 1; + + for ( int i = 0, n = *ptr.iPtr++; i < n; i++ ) + { + g.ordinates << *ptr.dPtr++; + g.ordinates << *ptr.dPtr++; + if ( dim == 3 ) + g.ordinates << *ptr.dPtr++; + iOrdinate += dim; + } + + ptr.ucPtr++; // Skip endianess of next linestring + ptr.iPtr++; // Skip type of next linestring + } + } + break; + + case QGis::WKBPolygon25D: + case QGis::WKBMultiPolygon25D: + dim = 3; + case QGis::WKBPolygon: + case QGis::WKBMultiPolygon: + { + g.gtype = SDO_GTYPE( dim, gtPolygon ); + int nPolygons = 1; + if ( type == QGis::WKBMultiPolygon25D || type == QGis::WKBMultiPolygon ) + { + g.gtype = SDO_GTYPE( dim, gtMultiPolygon ); + nPolygons = *ptr.iPtr++; + + ptr.ucPtr++; // Skip endianess of first polygon + ptr.iPtr++; // Skip type of first polygon + } + + for ( int iPolygon = 0; iPolygon < nPolygons; iPolygon++ ) + { + for ( int iRing = 0, nRings = *ptr.iPtr++; iRing < nRings; iRing++ ) + { + g.eleminfo << iOrdinate << ( iRing == 0 ? 1003 : 2003 ) << 1; + + // TODO: verify ring orientation + for ( int i = 0, n = *ptr.iPtr++; i < n; i++ ) + { + g.ordinates << *ptr.dPtr++; + g.ordinates << *ptr.dPtr++; + if ( dim == 3 ) + g.ordinates << *ptr.dPtr++; + iOrdinate += dim; + } + } + + ptr.ucPtr++; // Skip endianess of next polygon + ptr.iPtr++; // Skip type of next polygon + } + } + break; + + case QGis::WKBMultiPoint25D: + dim = 3; + case QGis::WKBMultiPoint: + { + g.gtype = SDO_GTYPE( dim, gtMultiPoint ); + int n = *ptr.iPtr++; + + g.eleminfo << 1 << 1 << n; + + for ( int i = 0; i < n; i++ ) + { + ptr.ucPtr++; // Skip endianess of point + ptr.iPtr++; // Skip type of point + + g.ordinates << *ptr.dPtr++; + g.ordinates << *ptr.dPtr++; + if ( dim == 3 ) + g.ordinates << *ptr.dPtr++; + iOrdinate += dim; + } + } + break; + + case QGis::WKBUnknown: + case QGis::WKBNoGeometry: + g.isNull = true; + break; + } + } + + QgsDebugMsgLevel( QString( "addBindValue geometry: isNull=%1 gtype=%2 srid=%3 p=%4,%5,%6 eleminfo=%7 ordinates=%8" ) + .arg( g.isNull ) + .arg( g.gtype ) + .arg( g.srid ) + .arg( g.x ).arg( g.y ).arg( g.z ) + .arg( g.eleminfo.size() ) + .arg( g.ordinates.size() ) + , 4 ); + qry.addBindValue( QVariant::fromValue( g ) ); +} + +bool QgsOracleProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) +{ + QgsDebugMsg( "entering." ); + + if ( mIsQuery || mGeometryColumn.isNull() ) + return false; + + QSqlDatabase db( *mConnection ); + + bool returnvalue = true; + + try + { + QSqlQuery qry( db ); + + if ( !db.transaction() ) + { + QgsMessageLog::logMessage( tr( "Could not start transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + + QString update = QString( "UPDATE %1 SET %2=? WHERE %3" ) + .arg( mQuery ) + .arg( quotedIdentifier( mGeometryColumn ) ) + .arg( pkParamWhereClause() ); + QgsDebugMsgLevel( QString( "SQL prepare: %1" ).arg( update ), 4 ); + if ( !qry.prepare( update ) ) + { + QgsMessageLog::logMessage( tr( "Could not prepare update statement.\nSQL: %1\nERROR: %2" ) + .arg( qry.lastQuery() ) + .arg( qry.lastError().text() ), + tr( "Oracle" ) ); + throw OracleException( qry ); + } + + for ( QgsGeometryMap::iterator iter = geometry_map.begin(); + iter != geometry_map.end(); + ++iter ) + { + appendGeomParam( &iter.value(), qry ); + appendPkParams( iter.key(), qry ); + + if ( !qry.exec() ) + throw OracleException( qry ); + } + + qry.finish(); + + if ( !db.commit() ) + { + QgsMessageLog::logMessage( tr( "Could not commit transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + } + catch ( OracleException &e ) + { + pushError( tr( "Oracle error while changing geometry values: %1" ).arg( e.errorMessage() ) ); + if ( !db.rollback() ) + { + QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) ); + } + returnvalue = false; + } + + rewind(); + + QgsDebugMsg( "exiting." ); + + return returnvalue; +} + +QgsAttributeList QgsOracleProvider::attributeIndexes() +{ + return mAttributeFields.keys(); +} + +int QgsOracleProvider::capabilities() const +{ + return mEnabledCapabilities; +} + +bool QgsOracleProvider::setSubsetString( QString theSQL, bool updateFeatureCount ) +{ + QString prevWhere = mSqlWhereClause; + + mSqlWhereClause = theSQL.trimmed(); + + QString sql = QString( "SELECT * FROM %1 WHERE " ).arg( mQuery ); + + if ( !mSqlWhereClause.isEmpty() ) + { + sql += "(" + mSqlWhereClause + ") AND "; + } + + sql += "rownum=0"; + + QSqlQuery qry( *mConnection ); + if ( !exec( qry, sql ) ) + { + pushError( qry.lastError().text() ); + mSqlWhereClause = prevWhere; + qry.finish(); + return false; + } + qry.finish(); + + if ( mPrimaryKeyType == pktInt && !uniqueData( mQuery, mAttributeFields[ mPrimaryKeyAttrs[0] ].name() ) ) + { + mSqlWhereClause = prevWhere; + return false; + } + + // Update datasource uri too + mUri.setSql( theSQL ); + // Update yet another copy of the uri. Why are there 3 copies of the + // uri? Perhaps this needs some rationalisation..... + setDataSourceUri( mUri.uri() ); + + if ( updateFeatureCount ) + { + mFeaturesCounted = -1; + } + mLayerExtent.setMinimal(); + + return true; +} + +/** + * Return the feature count + */ +long QgsOracleProvider::featureCount() const +{ + if ( mFeaturesCounted >= 0 ) + return mFeaturesCounted; + + // get total number of features + QString sql; + + // use estimated metadata even when there is a where clause, + // although we get an incorrect feature count for the subset + // - but make huge dataset usable. + if ( !mIsQuery && mUseEstimatedMetadata ) + { + sql = QString( "SELECT num_rows FROM all_tables WHERE owner=%1 AND table_name=%2" ) + .arg( quotedValue( mOwnerName ) ) + .arg( quotedValue( mTableName ) ); + } + else + { + sql = QString( "SELECT count(*) FROM %1" ).arg( mQuery ); + + if ( !mSqlWhereClause.isEmpty() ) + { + sql += " WHERE " + mSqlWhereClause; + } + } + + QSqlQuery qry( *mConnection ); + if ( exec( qry, sql ) && qry.next() ) + { + mFeaturesCounted = qry.value( 0 ).toInt(); + } + qry.finish(); + + QgsDebugMsg( "number of features: " + QString::number( mFeaturesCounted ) ); + + return mFeaturesCounted; +} + +QgsRectangle QgsOracleProvider::extent() +{ + if ( mGeometryColumn.isNull() ) + return QgsRectangle(); + + if ( mLayerExtent.isEmpty() ) + { + QString sql; + QSqlQuery qry( *mConnection ); + + if ( !mSpatialIndex.isNull() && ( mUseEstimatedMetadata || mSqlWhereClause.isEmpty() ) ) + { + sql = QString( "SELECT SDO_TUNE.EXTENT_OF(%1,%2) FROM dual" ) + .arg( quotedValue( QString( "%1.%2" ).arg( mOwnerName ).arg( mTableName ) ) ) + .arg( quotedValue( mGeometryColumn ) ); + } + else + { + sql = QString( "SELECT SDO_AGGR_MBR(%1) FROM %2" ).arg( quotedIdentifier( mGeometryColumn ) ).arg( mQuery ); + + if ( !mSqlWhereClause.isEmpty() ) + sql += QString( " WHERE %1" ).arg( mSqlWhereClause ); + } + + if ( exec( qry, sql ) && qry.next() ) + { + QByteArray *ba = static_cast( qry.value( 0 ).data() ); + unsigned char *copy = new unsigned char[ba->size()]; + memcpy( copy, ba->constData(), ba->size() ); + QgsGeometry g; + g.fromWkb( copy, ba->size() ); // take ownership + mLayerExtent = g.boundingBox(); + QgsDebugMsg( "extent: " + mLayerExtent.toString() ); + } + else + { + QgsMessageLog::logMessage( tr( "Could not retrieve extents: %1\nSQL: %2" ).arg( qry.lastError().text() ).arg( qry.lastQuery() ), tr( "Oracle" ) ); + } + } + + return mLayerExtent; +} + +void QgsOracleProvider::updateExtents() +{ + mLayerExtent.setMinimal(); +} + +bool QgsOracleProvider::getGeometryDetails() +{ + if ( mGeometryColumn.isNull() ) + { + mDetectedGeomType = QGis::WKBNoGeometry; + mValid = true; + return true; + } + + QString ownerName = mOwnerName; + QString tableName = mTableName; + QString geomCol = mGeometryColumn; + + QSqlQuery qry( *mConnection ); + if ( mIsQuery ) + { + if ( !exec( qry, QString( "SELECT %1 FROM %2 WHERE rownum=0" ).arg( quotedIdentifier( mGeometryColumn ) ).arg( mQuery ) ) ) + { + QgsMessageLog::logMessage( tr( "Could not execute query.\nThe error message from the database was:\n%1.\nSQL: %2" ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + mValid = false; + return false; + } + + ownerName = ""; + tableName = mQuery; + } + + int detectedSrid = -1; + QGis::WkbType detectedType = QGis::WKBUnknown; + mSpatialIndex = QString::null; + + if ( !ownerName.isEmpty() ) + { + if ( exec( qry, QString( "SELECT srid FROM all_sdo_geom_metadata WHERE owner=%1 AND table_name=%2 AND column_name=%3" ) + .arg( quotedValue( ownerName ) ) + .arg( quotedValue( tableName ) ) + .arg( quotedValue( geomCol ) ) ) ) + { + if ( qry.next() ) + { + detectedSrid = qry.value( 0 ).toInt(); + } + else + { + QgsMessageLog::logMessage( tr( "Could not retrieve SRID of %1.\nThe error message from the database was:\n%2.\nSQL: %3" ) + .arg( mQuery ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + } + } + else + { + QgsMessageLog::logMessage( tr( "Could not determine SRID of %1.\nThe error message from the database was:\n%2.\nSQL: %3" ) + .arg( mQuery ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + } + + if ( exec( qry, QString( mUseEstimatedMetadata + ? "SELECT DISTINCT gtype FROM (SELECT t.%1.sdo_gtype AS gtype FROM %2 t WHERE rownum<1000) WHERE rownum<=2" + : "SELECT DISTINCT t.%1.sdo_gtype FROM %2 t WHERE rownum<=2" ).arg( quotedIdentifier( geomCol ) ).arg( mQuery ) ) ) + { + if ( qry.next() ) + { + detectedType = QgsOracleConn::wkbTypeFromDatabase( qry.value( 0 ).toInt() ); + if ( qry.next() ) + { + detectedType = QGis::WKBUnknown; + } + } + else + { + detectedType = QGis::WKBUnknown; + QgsMessageLog::logMessage( tr( "%1 has no valid geometry types.\nSQL: %2" ) + .arg( mQuery ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + } + } + else + { + QgsMessageLog::logMessage( tr( "Could not determine geometry type of %1.\nThe error message from the database was:\n%2.\nSQL: %3" ) + .arg( mQuery ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + } + } + + if ( detectedType == QGis::WKBUnknown ) + { + QgsOracleLayerProperty layerProperty; + layerProperty.ownerName = ownerName; + layerProperty.tableName = tableName; + layerProperty.geometryColName = mGeometryColumn; + + QString delim = ""; + + if ( !mSqlWhereClause.isEmpty() ) + { + layerProperty.sql += delim + "(" + mSqlWhereClause + ")"; + delim = " AND "; + } + + mConnection->retrieveLayerTypes( layerProperty, mUseEstimatedMetadata ); + + Q_ASSERT( layerProperty.types.size() == layerProperty.srids.size() ); + + if ( layerProperty.types.isEmpty() ) + { + // no data - so take what's requested + if ( mRequestedGeomType == QGis::WKBUnknown ) + { + QgsMessageLog::logMessage( tr( "Geometry type and srid for empty column %1 of %2 undefined." ).arg( mGeometryColumn ).arg( mQuery ) ); + } + + detectedType = QGis::WKBUnknown; + detectedSrid = -1; + } + else + { + // requested type && srid is available + if ( mRequestedGeomType == QGis::WKBUnknown || layerProperty.types.contains( mRequestedGeomType ) ) + { + if ( layerProperty.size() == 1 ) + { + // only what we requested is available + detectedType = layerProperty.types.at( 0 ); + detectedSrid = layerProperty.srids.at( 0 ); + } + else + { + // we need to filter + detectedType = QGis::WKBUnknown; + detectedSrid = -1; + } + } + else + { + // geometry type undetermined or not unrequested + QgsMessageLog::logMessage( tr( "Feature type or srid for %1 of %2 could not be determined or was not requested." ).arg( mGeometryColumn ).arg( mQuery ) ); + detectedType = QGis::WKBUnknown; + detectedSrid = -1; + } + } + } + + mDetectedGeomType = detectedType; + if ( detectedSrid != -1 ) + mSrid = detectedSrid; + + QgsDebugMsg( QString( "Detected Oracle SRID is %1" ).arg( mSrid ) ); + QgsDebugMsg( QString( "Detected type is %1" ).arg( mDetectedGeomType ) ); + QgsDebugMsg( QString( "Requested type is %1" ).arg( mRequestedGeomType ) ); + + mValid = ( mDetectedGeomType != QGis::WKBUnknown || mRequestedGeomType != QGis::WKBUnknown ); + + if ( !mValid ) + return false; + + + // store whether the geometry includes measure value + if ( detectedType == QGis::WKBPoint25D || detectedType == QGis::WKBMultiPoint25D || + detectedType == QGis::WKBLineString25D || detectedType == QGis::WKBMultiLineString25D || + detectedType == QGis::WKBPolygon25D || detectedType == QGis::WKBMultiPolygon25D ) + { + // explicitly disable adding new features and editing of geometries + // as this would lead to corruption of measures + QgsMessageLog::logMessage( tr( "Editing and adding disabled for 2D+ layer (%1; %2)" ).arg( mGeometryColumn ).arg( mQuery ) ); + mEnabledCapabilities &= ~( QgsVectorDataProvider::ChangeGeometries | QgsVectorDataProvider::AddFeatures ); + } + + QgsDebugMsg( QString( "Feature type name is %1" ).arg( QGis::featureType( geometryType() ) ) ); + + return mValid; +} + +bool QgsOracleProvider::createSpatialIndex() +{ + QSqlQuery qry( *mConnection ); + + if ( !crs().geographicFlag() ) + { + // TODO: make precision configurable + QgsRectangle r( extent() ); + exec( qry, QString( "UPDATE user_sdo_geom_metadata SET diminfo=mdsys.sdo_dim_array(" + "mdsys.sdo_dim_element('X', %1, %2, 0.001)," + "mdsys.sdo_dim_element('Y', %3, %4, 0.001)" + ") WHERE table_name=%5 AND column_name=%6" ) + .arg( r.xMinimum(), 0, 'f', 16 ).arg( r.xMaximum(), 0, 'f', 16 ) + .arg( r.yMinimum(), 0, 'f', 16 ).arg( r.yMaximum(), 0, 'f', 16 ) + .arg( quotedValue( mTableName ) ) + .arg( quotedValue( mGeometryColumn ) ) ); + } + else + { + QgsDebugMsg( "geographic CRS" ); + } + + if ( mSpatialIndex.isNull() ) + { + int n = 0; + if ( exec( qry, QString( "SELECT coalesce(substr(max(index_name),10),'0') FROM all_indexes WHERE index_name LIKE 'QGIS_IDX_%' ESCAPE '#' ORDER BY index_name" ) ) && + qry.next() ) + { + n = qry.value( 0 ).toInt() + 1; + } + + if ( !exec( qry, QString( "CREATE INDEX QGIS_IDX_%1 ON %2.%3(%4) INDEXTYPE IS MDSYS.SPATIAL_INDEX PARALLEL" ) + .arg( n, 10, 10, QChar( '0' ) ) + .arg( quotedIdentifier( mOwnerName ) ) + .arg( quotedIdentifier( mTableName ) ) + .arg( quotedIdentifier( mGeometryColumn ) ) ) ) + { + QgsMessageLog::logMessage( tr( "Creation spatial index failed.\nSQL:%1\nError: %2" ) + .arg( qry.lastQuery() ) + .arg( qry.lastError().text() ), + tr( "Oracle" ) ); + return false; + } + + mSpatialIndex = QString( "QGIS_IDX_%1" ).arg( n, 10, 10, QChar( '0' ) ); + } + else + { + if ( !exec( qry, QString( "ALTER INDEX %1 REBUILD" ).arg( mSpatialIndex ) ) ) + { + QgsMessageLog::logMessage( tr( "Rebuild of spatial index failed.\nSQL:%1\nError: %2" ) + .arg( qry.lastQuery() ) + .arg( qry.lastError().text() ), + tr( "Oracle" ) ); + return false; + } + } + + return true; +} + +bool QgsOracleProvider::convertField( QgsField &field ) +{ + QString fieldType = "VARCHAR2(2047)"; //default to string + int fieldSize = field.length(); + int fieldPrec = field.precision(); + switch ( field.type() ) + { + case QVariant::LongLong: + fieldType = "NUMBER(20,0)"; + fieldSize = -1; + fieldPrec = 0; + break; + + case QVariant::String: + fieldType = "VARCHAR2(2047)"; + fieldPrec = -1; + break; + + case QVariant::Int: + fieldType = "NUMBER(10,0)"; + fieldSize = -1; + fieldPrec = 0; + break; + + case QVariant::Double: + if ( fieldSize <= 0 || fieldPrec <= 0 ) + { + fieldType = "BINARY_DOUBLE"; + fieldSize = -1; + fieldPrec = -1; + } + else + { + fieldType = QString( "NUMBER(%1,%2)" ).arg( fieldSize ).arg( fieldPrec ); + } + break; + + default: + return false; + } + + field.setTypeName( fieldType ); + field.setLength( fieldSize ); + field.setPrecision( fieldPrec ); + return true; +} + +QgsVectorLayerImport::ImportError QgsOracleProvider::createEmptyLayer( + const QString& uri, + const QgsFieldMap &fields, + QGis::WkbType wkbType, + const QgsCoordinateReferenceSystem *srs, + bool overwrite, + QMap *oldToNewAttrIdxMap, + QString *errorMessage, + const QMap *options ) +{ + Q_UNUSED( wkbType ); + Q_UNUSED( options ); + + // populate members from the uri structure + QgsDataSourceURI dsUri( uri ); + QString ownerName = dsUri.schema(); + + QgsDebugMsg( QString( "Connection info is: %1" ).arg( dsUri.connectionInfo() ) ); + + // create the table + QgsOracleConn *conn = QgsOracleConn::connectDb( dsUri.connectionInfo() ); + if ( !conn ) + { + if ( errorMessage ) + *errorMessage = QObject::tr( "Connection to database failed" ); + return QgsVectorLayerImport::ErrConnectionFailed; + } + + if ( ownerName.isEmpty() ) + { + ownerName = conn->currentUser(); + } + + if ( ownerName.isEmpty() ) + { + if ( errorMessage ) + *errorMessage = QObject::tr( "No owner name found" ); + return QgsVectorLayerImport::ErrInvalidLayer; + } + + QString tableName = dsUri.table(); + QString geometryColumn = dsUri.geometryColumn(); + + QString primaryKey = dsUri.keyColumn(); + QString primaryKeyType; + + QString ownerTableName = quotedIdentifier( ownerName ) + "." + quotedIdentifier( tableName ); + + QgsDebugMsg( QString( "Geometry column is: %1" ).arg( geometryColumn ) ); + QgsDebugMsg( QString( "Owner is: %1" ).arg( ownerName ) ); + QgsDebugMsg( QString( "Table name is: %1" ).arg( tableName ) ); + + // get the pk's name and type + + // if no pk name was passed, define the new pk field name + if ( primaryKey.isEmpty() ) + { + int index = 0; + QString pk = primaryKey = "id"; + for ( QgsFieldMap::const_iterator fldIt = fields.begin(); fldIt != fields.end(); ++fldIt ) + { + if ( fldIt.value().name() == primaryKey ) + { + // it already exists, try again with a new name + primaryKey = QString( "%1_%2" ).arg( pk ).arg( index++ ); + fldIt = fields.begin(); + } + } + } + else + { + // search for the passed field + for ( QgsFieldMap::const_iterator fldIt = fields.begin(); fldIt != fields.end(); ++fldIt ) + { + if ( fldIt.value().name() == primaryKey ) + { + // found, get the field type + QgsField fld = fldIt.value(); + if ( convertField( fld ) ) + { + primaryKeyType = fld.typeName(); + } + } + } + } + + QSqlDatabase db( *conn ); + QSqlQuery qry( db ); + try + { + if ( !db.transaction() ) + { + QgsMessageLog::logMessage( tr( "Could not start transaction" ), tr( "Oracle" ) ); + throw OracleException( qry ); + } + + if ( !exec( qry, QString( "SELECT 1 FROM all_tables WHERE owner=%1 AND table_name=%2" ) + .arg( quotedValue( ownerName ) ) + .arg( quotedValue( tableName ) ) + ) ) + { + throw OracleException( qry ); + } + + bool exists = qry.next(); + + if ( exists && overwrite ) + { + // delete the table if exists, then re-create it + if ( !exec( qry, QString( "DROP TABLE %1" ).arg( ownerTableName ) ) ) + throw OracleException( qry ); + } + + QString sql = QString( "CREATE TABLE %1(" ).arg( ownerTableName ); + QString delim; + + if ( !primaryKey.isEmpty() && !primaryKeyType.isEmpty() ) + { + sql += QString( "%1 %2 PRIMARY KEY" ).arg( quotedIdentifier( primaryKey ) ).arg( primaryKeyType ); + delim = ","; + } + + // create geometry column + sql += QString( "%1%2 MDSYS.SDO_GEOMETRY)" ).arg( delim ).arg( quotedIdentifier( geometryColumn ) ); + delim = ","; + + if ( !exec( qry, sql ) ) + { + throw OracleException( qry ); + } + + QStringList parts = srs->authid().split( ":" ); + if ( parts.size() != 2 ) + { + throw OracleException( qry ); + } + + // TODO: make precision configurable + QString diminfo; + if ( srs->geographicFlag() ) + { + diminfo = "mdsys.sdo_dim_array(" + "mdsys.sdo_dim_element('Longitude', -180, 180, 0.00001)," + "mdsys.sdo_dim_element('Latitude', -90, 90, 0.00001)" + ")"; + } + else + { + diminfo = "mdsys.sdo_dim_array(" + "mdsys.sdo_dim_element('X', NULL, NULL, 0.001)," + "mdsys.sdo_dim_element('Y', NULL, NULL, 0.001)" + ")"; + } + + if ( !exec( qry, QString( "INSERT INTO user_sdo_geom_metadata(table_name,column_name,srid,diminfo) VALUES (%1,%2,%3,%4)" ) + .arg( quotedValue( tableName.toUpper() ) ) + .arg( quotedValue( geometryColumn.toUpper() ) ) + .arg( parts[1].toInt() ) + .arg( diminfo ) ) ) + { + throw OracleException( qry ); + } + + if ( !db.commit() ) + { + QgsMessageLog::logMessage( tr( "Could not commit transaction" ), tr( "Oracle" ) ); + } + } + catch ( OracleException &e ) + { + if ( errorMessage ) + *errorMessage = QObject::tr( "Creation of data source %1 failed: \n%2" ) + .arg( ownerTableName ) + .arg( e.errorMessage() ); + + if ( db.rollback() ) + { + QgsMessageLog::logMessage( tr( "Could not rollback transaction" ), tr( "Oracle" ) ); + } + + conn->disconnect(); + + return QgsVectorLayerImport::ErrCreateLayer; + } + + conn->disconnect(); + + QgsDebugMsg( QString( "layer %1 created" ).arg( ownerTableName ) ); + + // use the provider to edit the table + dsUri.setDataSource( ownerName, tableName, geometryColumn, QString(), primaryKey ); + QgsOracleProvider *provider = new QgsOracleProvider( dsUri.uri() ); + if ( !provider->isValid() ) + { + if ( errorMessage ) + *errorMessage = QObject::tr( "Loading of the layer %1 failed" ).arg( ownerTableName ); + + delete provider; + return QgsVectorLayerImport::ErrInvalidLayer; + } + + QgsDebugMsg( "layer loaded" ); + + // add fields to the layer + if ( oldToNewAttrIdxMap ) + oldToNewAttrIdxMap->clear(); + + if ( fields.size() > 0 ) + { + QSet names; + QList flist; + QMap attrMap; + for ( QgsFieldMap::const_iterator fldIt = fields.begin(); fldIt != fields.end(); ++fldIt ) + { + QgsField fld = fldIt.value(); + + QString name = fld.name().left( 30 ).toUpper(); + + if ( names.contains( name ) ) + { + int j; + int n; + for ( j = 1, n = 10; j < 3; j++, n *= 10 ) + { + int k; + for ( k = 0; k < n && names.contains( name ); k++ ) + { + name = QString( "%1%2" ).arg( name.left( 30 - j ) ).arg( k, j, 10, QChar( '0' ) ); + } + + if ( k < n ) + break; + } + + if ( j == 3 ) + { + if ( errorMessage ) + *errorMessage = QObject::tr( "Field name clash found (%1 not remappable)" ).arg( fld.name() ); + + delete provider; + return QgsVectorLayerImport::ErrAttributeTypeUnsupported; + } + } + + if ( oldToNewAttrIdxMap ) + { + attrMap.insert( fld.name(), name ); + } + + fld.setName( name ); + + names << name; + + if ( !convertField( fld ) ) + { + if ( errorMessage ) + *errorMessage = QObject::tr( "Unsupported type for field %1" ).arg( fld.name() ); + + delete provider; + return QgsVectorLayerImport::ErrAttributeTypeUnsupported; + } + + QgsDebugMsg( QString( "Field #%1 name %2 type %3 typename %4 width %5 precision %6" ) + .arg( fldIt.key() ) + .arg( fld.name() ).arg( QVariant::typeToName( fld.type() ) ).arg( fld.typeName() ) + .arg( fld.length() ).arg( fld.precision() ) ); + + flist << fld; + } + + if ( !provider->addAttributes( flist ) ) + { + if ( errorMessage ) + *errorMessage = QObject::tr( "Creation of fields failed" ); + + delete provider; + return QgsVectorLayerImport::ErrAttributeCreationFailed; + } + + if ( oldToNewAttrIdxMap ) + { + for ( QgsFieldMap::const_iterator fldIt = fields.begin(); fldIt != fields.end(); ++fldIt ) + { + QString dstName = attrMap.value( fldIt->name(), QString::null ); + if ( dstName.isNull() ) + continue; + + int dstIdx = provider->fieldNameIndex( dstName ); + QgsDebugMsg( QString( "Map #%1 (%2) to #%3 (%4)" ) + .arg( fldIt.key() ).arg( fldIt->name() ) + .arg( dstIdx ).arg( dstName ) ); + oldToNewAttrIdxMap->insert( fldIt.key(), dstIdx ); + } + } + + QgsDebugMsg( "Done creating fields" ); + } + else + { + QgsDebugMsg( "No fields created." ); + } + + delete provider; + + return QgsVectorLayerImport::NoError; +} + +QgsCoordinateReferenceSystem QgsOracleProvider::crs() +{ + QgsCoordinateReferenceSystem srs; + srs.createFromOgcWmsCrs( QString( "EPSG:%1" ).arg( mSrid ) ); + return srs; +} + +QString QgsOracleProvider::subsetString() +{ + return mSqlWhereClause; +} + +QString QgsOracleProvider::getTableName() +{ + return mTableName; +} + +size_t QgsOracleProvider::layerCount() const +{ + return 1; // XXX need to return actual number of layers +} // QgsOracleProvider::layerCount() + + +QString QgsOracleProvider::name() const +{ + return ORACLE_KEY; +} // QgsOracleProvider::name() + +QString QgsOracleProvider::description() const +{ + return ORACLE_DESCRIPTION; +} // QgsOracleProvider::description() + +/** + * Class factory to return a pointer to a newly created + * QgsOracleProvider object + */ +QGISEXTERN QgsOracleProvider * classFactory( const QString *uri ) +{ + return new QgsOracleProvider( *uri ); +} +/** Required key function (used to map the plugin to a data store type) +*/ +QGISEXTERN QString providerKey() +{ + return QSqlDatabase::isDriverAvailable( "QOCISPATIAL" ) ? ORACLE_KEY : 0; +} +/** + * Required description function + */ +QGISEXTERN QString description() +{ + return QSqlDatabase::isDriverAvailable( "QOCISPATIAL" ) ? ORACLE_DESCRIPTION : 0; +} +/** + * Required isProvider function. Used to determine if this shared library + * is a data provider plugin + */ +QGISEXTERN bool isProvider() +{ + return true; +} + +QGISEXTERN QgsOracleSourceSelect *selectWidget( QWidget *parent, Qt::WFlags fl ) +{ + return new QgsOracleSourceSelect( parent, fl ); +} + +QGISEXTERN int dataCapabilities() +{ + return QgsDataProvider::Database; +} + +QGISEXTERN QgsDataItem *dataItem( QString thePath, QgsDataItem *parentItem ) +{ + Q_UNUSED( thePath ); + return new QgsOracleRootItem( parentItem, "Oracle", "oracle:" ); +} + +// --------------------------------------------------------------------------- + +QGISEXTERN QgsVectorLayerImport::ImportError createEmptyLayer( + const QString& uri, + const QgsFieldMap &fields, + QGis::WkbType wkbType, + const QgsCoordinateReferenceSystem *srs, + bool overwrite, + QMap *oldToNewAttrIdxMap, + QString *errorMessage, + const QMap *options ) +{ + return QgsOracleProvider::createEmptyLayer( + uri, fields, wkbType, srs, overwrite, + oldToNewAttrIdxMap, errorMessage, options + ); +} + +QGISEXTERN bool deleteLayer( const QString& uri, QString& errCause ) +{ + QgsDebugMsg( "deleting layer " + uri ); + + QgsDataSourceURI dsUri( uri ); + QString ownerName = dsUri.schema(); + QString tableName = dsUri.table(); + QString geometryCol = dsUri.geometryColumn(); + + QgsOracleConn* conn = QgsOracleConn::connectDb( dsUri.connectionInfo() ); + if ( !conn ) + { + errCause = QObject::tr( "Connection to database failed" ); + conn->disconnect(); + return false; + } + + if ( ownerName != conn->currentUser() ) + { + errCause = QObject::tr( "%1 not owner of the table %2." ) + .arg( ownerName ) + .arg( tableName ); + conn->disconnect(); + return false; + } + + QSqlQuery qry( *conn ); + + // check the geometry column count + if ( !QgsOracleProvider::exec( qry, QString( "SELECT count(*)" + " FROM user_tab_columns" + " WHERE table_name=%1 AND data_type='SDO_GEOMETRY' AND data_type_owner='MDSYS'" ) + .arg( QgsOracleConn::quotedValue( tableName ) ) ) + || !qry.next() ) + { + errCause = QObject::tr( "Unable determine number of geometry columns of layer %1.%2: \n%3" ) + .arg( ownerName ) + .arg( tableName ) + .arg( qry.lastError().text() ); + conn->disconnect(); + return false; + } + + int count = qry.value( 0 ).toInt(); + + QString dropTable; + QString cleanView; + if ( !geometryCol.isEmpty() && count > 1 ) + { + // the table has more geometry columns, drop just the geometry column + dropTable = QString( "ALTER TABLE %1 DROP COLUMN %2" ) + .arg( QgsOracleConn::quotedIdentifier( tableName ) ) + .arg( QgsOracleConn::quotedIdentifier( geometryCol ) ); + cleanView = QString( "DELETE FROM user_sdo_geom_metadata WHERE table_name=%1 AND column_name=%2" ) + .arg( QgsOracleConn::quotedValue( tableName ) ) + .arg( QgsOracleConn::quotedValue( geometryCol ) ); + } + else + { + // drop the table + dropTable = QString( "DROP TABLE %1" ) + .arg( QgsOracleConn::quotedIdentifier( tableName ) ); + cleanView = QString( "DELETE FROM user_sdo_geom_metadata WHERE table_name=%1" ) + .arg( QgsOracleConn::quotedValue( tableName ) ); + } + + if ( !QgsOracleProvider::exec( qry, dropTable ) ) + { + errCause = QObject::tr( "Unable to delete layer %1.%2: \n%3" ) + .arg( ownerName ) + .arg( tableName ) + .arg( qry.lastError().text() ); + conn->disconnect(); + return false; + } + + if ( !QgsOracleProvider::exec( qry, cleanView ) ) + { + errCause = QObject::tr( "Unable to clean metadata %1.%2: \n%3" ) + .arg( ownerName ) + .arg( tableName ) + .arg( qry.lastError().text() ); + conn->disconnect(); + return false; + } + + conn->disconnect(); + return true; +} + +// vim: set sw=2 : diff --git a/src/providers/oracle/qgsoracleprovider.h b/src/providers/oracle/qgsoracleprovider.h new file mode 100644 index 000000000000..8c34253e6bf0 --- /dev/null +++ b/src/providers/oracle/qgsoracleprovider.h @@ -0,0 +1,427 @@ +/*************************************************************************** + qgsoracleprovider.h - Data provider for oracle layers + ------------------- + begin : August 2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSORACLEPROVIDER_H +#define QGSORACLEPROVIDER_H + +#include "qgsvectordataprovider.h" +#include "qgsrectangle.h" +#include "qgsvectorlayerimport.h" +#include "qgsoracletablemodel.h" +#include "qgsdatasourceuri.h" + +#include +#include +#include +#include + +class QgsFeature; +class QgsField; +class QgsGeometry; + +#include "qgsdatasourceuri.h" +#include "ocispatial/wkbptr.h" + +/** + \class QgsOracleProvider + \brief Data provider for oracle layers. + + This provider implements the + interface defined in the QgsDataProvider class to provide access to spatial + data residing in a oracle enabled database. + */ +class QgsOracleProvider : public QgsVectorDataProvider +{ + Q_OBJECT + + public: + + /** Import a vector layer into the database */ + static QgsVectorLayerImport::ImportError createEmptyLayer( + const QString& uri, + const QgsFieldMap &fields, + QGis::WkbType wkbType, + const QgsCoordinateReferenceSystem *srs, + bool overwrite, + QMap *oldToNewAttrIdxMap, + QString *errorMessage = 0, + const QMap *options = 0 + ); + + /** + * Constructor for the provider. The uri must be in the following format: + * host=localhost user=gsherman dbname=test password=xxx table=test.alaska (the_geom) + * @param uri String containing the required parameters to connect to the database + * and query the table. + */ + QgsOracleProvider( QString const &uri = "" ); + + //! Destructor + virtual ~QgsOracleProvider(); + + /** + * Returns the permanent storage type for this layer as a friendly name. + */ + virtual QString storageType() const; + + /*! Get the QgsCoordinateReferenceSystem for this layer + * @note Must be reimplemented by each provider. + * If the provider isn't capable of returning + * its projection an empty srs will be returned + */ + virtual QgsCoordinateReferenceSystem crs(); + + /** Select features based on a bounding rectangle. Features can be retrieved with calls to nextFeature. + * @param fetchAttributes list of attributes which should be fetched + * @param rect spatial filter + * @param fetchGeometry true if the feature geometry should be fetched + * @param useIntersect true if an accurate intersection test should be used, + * false if a test based on bounding box is sufficient + */ + virtual void select( QgsAttributeList fetchAttributes = QgsAttributeList(), + QgsRectangle rect = QgsRectangle(), + bool fetchGeometry = true, + bool useIntersect = false ); + + /** + * Get the next feature resulting from a select operation. + * @param feature feature which will receive data from the provider + * @return true when there was a feature to fetch, false when end was hit + */ + virtual bool nextFeature( QgsFeature& feature ); + + /** + * Gets the feature at the given feature ID. + * @param featureId id of the feature + * @param feature feature which will receive the data + * @param fetchGeoemtry if true, geometry will be fetched from the provider + * @param fetchAttributes a list containing the indexes of the attribute fields to copy + * @return True when feature was found, otherwise false + */ + virtual bool featureAtId( QgsFeatureId featureId, + QgsFeature& feature, + bool fetchGeometry = true, + QgsAttributeList fetchAttributes = QgsAttributeList() ); + + /** Get the feature type. This corresponds to + * WKBPoint, + * WKBLineString, + * WKBPolygon, + * WKBMultiPoint, + * WKBMultiLineString or + * WKBMultiPolygon + * as defined in qgis.h + */ + QGis::WkbType geometryType() const; + + /** return the number of layers for the current data source + * @note Should this be subLayerCount() instead? + */ + size_t layerCount() const; + + /** + * Get the number of features in the layer + */ + long featureCount() const; + + /** + * Get the number of fields in the layer + */ + uint fieldCount() const; + + /** + * Return a string representation of the endian-ness for the layer + */ + QString endianString(); + + /** + * Changes the stored extent for this layer to the supplied extent. + * For example, this is called when the extent worker thread has a result. + */ + void setExtent( QgsRectangle& newExtent ); + + /** Return the extent for this data layer + */ + virtual QgsRectangle extent(); + + /** Update the extent + */ + virtual void updateExtents(); + + /** Determine the fields making up the primary key + */ + bool determinePrimaryKey(); + + /** + * Get the field information for the layer + * @return vector of QgsField objects + */ + const QgsFieldMap &fields() const; + + /** + * Return a short comment for the data that this provider is + * providing access to (e.g. the comment for oracle table). + */ + QString dataComment() const; + + /** Reset the layer + */ + void rewind(); + + /** Returns the minimum value of an attribute + * @param index the index of the attribute */ + QVariant minimumValue( int index ); + + /** Returns the maximum value of an attribute + * @param index the index of the attribute */ + QVariant maximumValue( int index ); + + /** Return the unique values of an attribute + * @param index the index of the attribute + * @param values reference to the list of unique values */ + virtual void uniqueValues( int index, QList &uniqueValues, int limit = -1 ); + + /**Returns true if layer is valid + */ + bool isValid(); + + QgsAttributeList attributeIndexes(); + + QgsAttributeList pkAttributeIndexes() { return mPrimaryKeyAttrs; } + + /**Returns the default value for field specified by @c fieldName */ + QVariant defaultValue( QString fieldName, QString tableName = QString::null, QString schemaName = QString::null ); + + /**Returns the default value for field specified by @c fieldId */ + QVariant defaultValue( int fieldId ); + + /**Adds a list of features + @return true in case of success and false in case of failure*/ + bool addFeatures( QgsFeatureList & flist ); + + /**Deletes a list of features + @param id list of feature ids + @return true in case of success and false in case of failure*/ + bool deleteFeatures( const QgsFeatureIds & id ); + + /**Adds new attributes + @param name map with attribute name as key and type as value + @return true in case of success and false in case of failure*/ + bool addAttributes( const QList &attributes ); + + /**Deletes existing attributes + @param names of the attributes to delete + @return true in case of success and false in case of failure*/ + bool deleteAttributes( const QgsAttributeIds & name ); + + /**Changes attribute values of existing features + @param attr_map a map containing the new attributes. The integer is the feature id, + the first QString is the attribute name and the second one is the new attribute value + @return true in case of success and false in case of failure*/ + bool changeAttributeValues( const QgsChangedAttributesMap & attr_map ); + + /** + Changes geometries of existing features + @param geometry_map A QMap containing the feature IDs to change the geometries of. + the second map parameter being the new geometries themselves + @return true in case of success and false in case of failure + */ + bool changeGeometryValues( QgsGeometryMap & geometry_map ); + + /**Tries to create an spatial index file for faster access if only a subset of the features is required + @return true in case of success*/ + bool createSpatialIndex(); + + //! Get the table name associated with this provider instance + QString getTableName(); + + /** Accessor for sql where clause used to limit dataset */ + QString subsetString(); + + /** mutator for sql where clause used to limit dataset size */ + bool setSubsetString( QString theSQL, bool updateFeatureCount = true ); + + virtual bool supportsSubsetString() { return true; } + + /**Returns a bitmask containing the supported capabilities*/ + int capabilities() const; + + /** return a provider name + + Essentially just returns the provider key. Should be used to build file + dialogs so that providers can be shown with their supported types. Thus + if more than one provider supports a given format, the user is able to + select a specific provider to open that file. + + @note + + Instead of being pure virtual, might be better to generalize this + behavior and presume that none of the sub-classes are going to do + anything strange with regards to their name or description? + + */ + QString name() const; + + /** return description + + Return a terse string describing what the provider is. + + @note + + Instead of being pure virtual, might be better to generalize this + behavior and presume that none of the sub-classes are going to do + anything strange with regards to their name or description? + + */ + QString description() const; + + static bool exec( QSqlQuery &qry, QString sql ); + + private: + bool openQuery( QString alias, + const QgsAttributeList &fetchAttributes, + bool fetchGeometry, + QString whereClause ); + + bool getFeature( bool fetchGeometry, + QgsFeature &feature, + const QgsAttributeList &fetchAttributes ); + + QString whereClause( QgsFeatureId featureId ) const; + QString pkParamWhereClause() const; + QString paramValue( QString fieldvalue, const QString &defaultValue ) const; + void appendGeomParam( QgsGeometry *geom, QSqlQuery &qry ) const; + void appendPkParams( QgsFeatureId fid, QSqlQuery &qry ) const; + + bool hasSufficientPermsAndCapabilities(); + + const QgsField &field( int index ) const; + + /** Load the field list + */ + bool loadFields(); + + /** convert a QgsField to work with Oracle */ + static bool convertField( QgsField &field ); + + QgsFieldMap mAttributeFields; + QMap mDefaultValues; + QString mDataComment; + + //! Data source URI struct for this layer + QgsDataSourceURI mUri; + + /** + * Flag indicating if the layer data source is a valid oracle layer + */ + bool mValid; + + /** + * provider references query (instead of a table) + */ + bool mIsQuery; + + /** + * Name of the table with no schema + */ + QString mTableName; + /** + * Name of the table or subquery + */ + QString mQuery; + /** + * Owner of the table + */ + QString mOwnerName; + /** + * SQL statement used to limit the features retrieved + */ + QString mSqlWhereClause; + + /** + * Data type for the primary key + */ + enum { pktUnknown, pktInt, pktRowId, pktFidMap } mPrimaryKeyType; + + /** + * List of primary key attributes for fetching features. + */ + QList mPrimaryKeyAttrs; + QString mPrimaryKeyDefault; + + QString mGeometryColumn; //! name of the geometry column + QgsRectangle mLayerExtent; //! Rectangle that contains the extent (bounding box) of the layer + mutable long mFeaturesCounted; //! Number of features in the layer + int mSrid; //! srid of column + int mEnabledCapabilities; //! capabilities of layer + + QGis::WkbType mDetectedGeomType; //! geometry type detected in the database + QGis::WkbType mRequestedGeomType; //! geometry type requested in the uri + + bool getGeometryDetails(); + + /* Use estimated metadata. Uses fast table counts, geometry type and extent determination */ + bool mUseEstimatedMetadata; + + struct OracleFieldNotFound {}; //! Exception to throw + + struct OracleException + { + OracleException( const QSqlQuery &q ) + : mWhat( tr( "Oracle-Error: %1\nSQL: %2\n" ).arg( q.lastError().text() ).arg( q.lastQuery() ) ) + {} + + OracleException( const OracleException &e ) + : mWhat( e.errorMessage() ) + {} + + ~OracleException() + {} + + QString errorMessage() const + { + return mWhat; + } + + private: + QString mWhat; + }; + + // A function that determines if the given schema.table.column + // contains unqiue entries + bool uniqueData( QString query, QString colName ); + + void disconnectDb(); + + static QString quotedIdentifier( QString ident ) { return QgsOracleConn::quotedIdentifier( ident ); } + static QString quotedValue( QVariant value ) { return QgsOracleConn::quotedValue( value ); } + + QgsFeatureId lookupFid( const QVariant &v ); //! lookup existing mapping or add a new one + + QMap mKeyToFid; //! map key values to feature id + QMap mFidToKey; //! map feature back to fea + QgsFeatureId mFidCounter; //! next feature id if map is used + QgsOracleConn *mConnection; + QSqlQuery mQry; + bool mFetchGeomRequested; //! geometry was requested + bool mIntersectResult; //! need to intersect the results (Oracle Locator only) + bool mHasSpatial; //! Oracle Spatial is installed + QString mSpatialIndex; //! name of spatial index of geometry column + QgsRectangle mIntersectRect; //! rectangle to intersect with (if any) +}; + +#endif diff --git a/src/providers/oracle/qgsoraclesourceselect.cpp b/src/providers/oracle/qgsoraclesourceselect.cpp new file mode 100644 index 000000000000..beb359fba967 --- /dev/null +++ b/src/providers/oracle/qgsoraclesourceselect.cpp @@ -0,0 +1,655 @@ +/*************************************************************************** + qgsoraclesourceselect.cpp + Dialog to select Oracle layer(s) and add it to the map canvas + ------------------- +begin : August 2012 +copyright : (C) 2012 by Juergen E. Fischer +email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsoraclesourceselect.h" +#include "qgsoraclecolumntypethread.h" + +#include "qgslogger.h" +#include "qgsapplication.h" +#include "qgscontexthelp.h" +#include "qgsoracleprovider.h" +#include "qgsoraclenewconnection.h" +#include "qgsmanageconnectionsdialog.h" +#include "qgsquerybuilder.h" +#include "qgsdatasourceuri.h" +#include "qgsvectorlayer.h" + +#include +#include +#include +#include +#include +#include +#include + +/** Used to create an editor for when the user tries to change the contents of a cell */ +QWidget *QgsOracleSourceSelectDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const +{ + Q_UNUSED( option ); + if ( index.column() == QgsOracleTableModel::dbtmSql ) + { + QLineEdit *le = new QLineEdit( parent ); + le->setText( index.data( Qt::DisplayRole ).toString() ); + return le; + } + + if ( index.column() == QgsOracleTableModel::dbtmType && index.data( Qt::UserRole + 1 ).toBool() ) + { + QComboBox *cb = new QComboBox( parent ); + foreach ( QGis::WkbType type, + QList() + << QGis::WKBPoint + << QGis::WKBLineString + << QGis::WKBPolygon + << QGis::WKBMultiPoint + << QGis::WKBMultiLineString + << QGis::WKBMultiPolygon + << QGis::WKBNoGeometry ) + { + cb->addItem( QgsOracleTableModel::iconForWkbType( type ), QgsOracleConn::displayStringForWkbType( type ), type ); + } + cb->setCurrentIndex( cb->findData( index.data( Qt::UserRole + 2 ).toInt() ) ); + return cb; + } + + if ( index.column() == QgsOracleTableModel::dbtmPkCol ) + { + bool isView = index.data( Qt::UserRole + 1 ).toBool(); + if ( !isView ) + return 0; + + QStringList values = index.data( Qt::UserRole + 2 ).toStringList(); + if ( values.size() == 0 ) + { + QString tableName = index.sibling( index.row(), QgsOracleTableModel::dbtmTable ).data( Qt::DisplayRole ).toString(); + QString ownerName = index.sibling( index.row(), QgsOracleTableModel::dbtmOwner ).data( Qt::DisplayRole ).toString(); + + values = mConn->pkCandidates( ownerName, tableName ); + } + + if ( values.size() == 0 ) + return 0; + + + if ( values.size() > 0 ) + { + QComboBox *cb = new QComboBox( parent ); + cb->addItems( values ); + cb->setCurrentIndex( cb->findText( index.data( Qt::DisplayRole ).toString() ) ); + return cb; + } + } + + if ( index.column() == QgsOracleTableModel::dbtmSrid ) + { + QLineEdit *le = new QLineEdit( parent ); + le->setValidator( new QIntValidator( -1, 999999, parent ) ); + le->insert( index.data( Qt::DisplayRole ).toString() ); + return le; + } + + return 0; +} + +void QgsOracleSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const +{ + QComboBox *cb = qobject_cast( editor ); + if ( cb ) + { + if ( index.column() == QgsOracleTableModel::dbtmType ) + { + QGis::WkbType type = ( QGis::WkbType ) cb->itemData( cb->currentIndex() ).toInt(); + + model->setData( index, QgsOracleTableModel::iconForWkbType( type ), Qt::DecorationRole ); + model->setData( index, type != QGis::WKBUnknown ? QgsOracleConn::displayStringForWkbType( type ) : tr( "Select..." ) ); + model->setData( index, type, Qt::UserRole + 2 ); + } + else if ( index.column() == QgsOracleTableModel::dbtmPkCol ) + { + model->setData( index, cb->currentText() ); + model->setData( index, cb->currentText(), Qt::UserRole + 2 ); + } + } + + QLineEdit *le = qobject_cast( editor ); + if ( le ) + model->setData( index, le->text() ); +} + +QgsOracleSourceSelect::QgsOracleSourceSelect( QWidget *parent, Qt::WFlags fl, bool managerMode, bool embeddedMode ) + : QDialog( parent, fl ) + , mManagerMode( managerMode ) + , mEmbeddedMode( embeddedMode ) + , mColumnTypeThread( 0 ) + , mIsConnected( false ) +{ + setupUi( this ); + + + if ( mEmbeddedMode ) + { + buttonBox->button( QDialogButtonBox::Close )->hide(); + } + else + { + setWindowTitle( tr( "Add Oracle Table(s)" ) ); + } + + mAddButton = new QPushButton( tr( "&Add" ) ); + mAddButton->setEnabled( false ); + + mBuildQueryButton = new QPushButton( tr( "&Build query" ) ); + mBuildQueryButton->setToolTip( tr( "Build query" ) ); + mBuildQueryButton->setDisabled( true ); + + if ( !mManagerMode ) + { + buttonBox->addButton( mAddButton, QDialogButtonBox::ActionRole ); + connect( mAddButton, SIGNAL( clicked() ), this, SLOT( addTables() ) ); + + buttonBox->addButton( mBuildQueryButton, QDialogButtonBox::ActionRole ); + connect( mBuildQueryButton, SIGNAL( clicked() ), this, SLOT( buildQuery() ) ); + } + + populateConnectionList(); + + mSearchModeComboBox->addItem( tr( "Wildcard" ) ); + mSearchModeComboBox->addItem( tr( "RegExp" ) ); + + mSearchColumnComboBox->addItem( tr( "All" ) ); + mSearchColumnComboBox->addItem( tr( "Owner" ) ); + mSearchColumnComboBox->addItem( tr( "Table" ) ); + mSearchColumnComboBox->addItem( tr( "Type" ) ); + mSearchColumnComboBox->addItem( tr( "Geometry column" ) ); + mSearchColumnComboBox->addItem( tr( "Primary key column" ) ); + mSearchColumnComboBox->addItem( tr( "SRID" ) ); + mSearchColumnComboBox->addItem( tr( "Sql" ) ); + + mProxyModel.setParent( this ); + mProxyModel.setFilterKeyColumn( -1 ); + mProxyModel.setFilterCaseSensitivity( Qt::CaseInsensitive ); + mProxyModel.setSourceModel( &mTableModel ); + + mTablesTreeDelegate = new QgsOracleSourceSelectDelegate( this ); + + mTablesTreeView->setModel( &mProxyModel ); + mTablesTreeView->setSortingEnabled( true ); + mTablesTreeView->setEditTriggers( QAbstractItemView::CurrentChanged ); + mTablesTreeView->setItemDelegate( mTablesTreeDelegate ); + + + QSettings settings; + mTablesTreeView->setSelectionMode( settings.value( "/qgis/addOracleDC", false ).toBool() ? + QAbstractItemView::ExtendedSelection : + QAbstractItemView::MultiSelection ); + + + //for Qt < 4.3.2, passing -1 to include all model columns + //in search does not seem to work + mSearchColumnComboBox->setCurrentIndex( 2 ); + + restoreGeometry( settings.value( "/Windows/PgSourceSelect/geometry" ).toByteArray() ); + + for ( int i = 0; i < mTableModel.columnCount(); i++ ) + { + mTablesTreeView->setColumnWidth( i, settings.value( QString( "/Windows/PgSourceSelect/columnWidths/%1" ).arg( i ), mTablesTreeView->columnWidth( i ) ).toInt() ); + } + + //hide the search options by default + //they will be shown when the user ticks + //the search options group box + mSearchLabel->setVisible( false ); + mSearchColumnComboBox->setVisible( false ); + mSearchColumnsLabel->setVisible( false ); + mSearchModeComboBox->setVisible( false ); + mSearchModeLabel->setVisible( false ); + mSearchTableEdit->setVisible( false ); +} +/** Autoconnected SLOTS **/ +// Slot for adding a new connection +void QgsOracleSourceSelect::on_btnNew_clicked() +{ + QgsOracleNewConnection *nc = new QgsOracleNewConnection( this ); + if ( nc->exec() ) + { + populateConnectionList(); + emit connectionsChanged(); + } + delete nc; +} +// Slot for deleting an existing connection +void QgsOracleSourceSelect::on_btnDelete_clicked() +{ + QString msg = tr( "Are you sure you want to remove the %1 connection and all associated settings?" ) + .arg( cmbConnections->currentText() ); + if ( QMessageBox::Ok != QMessageBox::information( this, tr( "Confirm Delete" ), msg, QMessageBox::Ok | QMessageBox::Cancel ) ) + return; + + QgsOracleConn::deleteConnection( cmbConnections->currentText() ); + + populateConnectionList(); + emit connectionsChanged(); +} + +void QgsOracleSourceSelect::on_btnSave_clicked() +{ + QgsManageConnectionsDialog dlg( this, QgsManageConnectionsDialog::Export, QgsManageConnectionsDialog::Oracle ); + dlg.exec(); +} + +void QgsOracleSourceSelect::on_btnLoad_clicked() +{ + QString fileName = QFileDialog::getOpenFileName( this, tr( "Load connections" ), ".", + tr( "XML files (*.xml *XML)" ) ); + if ( fileName.isEmpty() ) + { + return; + } + + QgsManageConnectionsDialog dlg( this, QgsManageConnectionsDialog::Import, QgsManageConnectionsDialog::Oracle, fileName ); + dlg.exec(); + populateConnectionList(); +} + +// Slot for editing a connection +void QgsOracleSourceSelect::on_btnEdit_clicked() +{ + QgsOracleNewConnection *nc = new QgsOracleNewConnection( this, cmbConnections->currentText() ); + if ( nc->exec() ) + { + populateConnectionList(); + emit connectionsChanged(); + } + delete nc; +} + +/** End Autoconnected SLOTS **/ + +// Remember which database is selected +void QgsOracleSourceSelect::on_cmbConnections_currentIndexChanged( const QString & text ) +{ + // Remember which database was selected. + QgsOracleConn::setSelectedConnection( text ); + + cbxAllowGeometrylessTables->blockSignals( true ); + cbxAllowGeometrylessTables->setChecked( QgsOracleConn::allowGeometrylessTables( text ) ); + cbxAllowGeometrylessTables->blockSignals( false ); +} + +void QgsOracleSourceSelect::on_cbxAllowGeometrylessTables_stateChanged( int ) +{ + if ( mIsConnected ) + on_btnConnect_clicked(); +} + +void QgsOracleSourceSelect::buildQuery() +{ + setSql( mTablesTreeView->currentIndex() ); +} + +void QgsOracleSourceSelect::on_mTablesTreeView_clicked( const QModelIndex &index ) +{ + mBuildQueryButton->setEnabled( index.parent().isValid() ); +} + +void QgsOracleSourceSelect::on_mTablesTreeView_doubleClicked( const QModelIndex &index ) +{ + QSettings settings; + if ( settings.value( "/qgis/addOracleDC", false ).toBool() ) + { + addTables(); + } + else + { + setSql( index ); + } +} + +void QgsOracleSourceSelect::on_mSearchTableEdit_textChanged( const QString & text ) +{ + if ( mSearchModeComboBox->currentText() == tr( "Wildcard" ) ) + { + mProxyModel._setFilterWildcard( text ); + } + else if ( mSearchModeComboBox->currentText() == tr( "RegExp" ) ) + { + mProxyModel._setFilterRegExp( text ); + } +} + +void QgsOracleSourceSelect::on_mSearchColumnComboBox_currentIndexChanged( const QString & text ) +{ + if ( text == tr( "All" ) ) + { + mProxyModel.setFilterKeyColumn( -1 ); + } + else if ( text == tr( "Owner" ) ) + { + mProxyModel.setFilterKeyColumn( QgsOracleTableModel::dbtmOwner ); + } + else if ( text == tr( "Table" ) ) + { + mProxyModel.setFilterKeyColumn( QgsOracleTableModel::dbtmTable ); + } + else if ( text == tr( "Type" ) ) + { + mProxyModel.setFilterKeyColumn( QgsOracleTableModel::dbtmType ); + } + else if ( text == tr( "Geometry column" ) ) + { + mProxyModel.setFilterKeyColumn( QgsOracleTableModel::dbtmGeomCol ); + } + else if ( text == tr( "Primary key column" ) ) + { + mProxyModel.setFilterKeyColumn( QgsOracleTableModel::dbtmPkCol ); + } + else if ( text == tr( "SRID" ) ) + { + mProxyModel.setFilterKeyColumn( QgsOracleTableModel::dbtmSrid ); + } + else if ( text == tr( "Sql" ) ) + { + mProxyModel.setFilterKeyColumn( QgsOracleTableModel::dbtmSql ); + } +} + +void QgsOracleSourceSelect::on_mSearchModeComboBox_currentIndexChanged( const QString & text ) +{ + Q_UNUSED( text ); + on_mSearchTableEdit_textChanged( mSearchTableEdit->text() ); +} + +void QgsOracleSourceSelect::setLayerType( QgsOracleLayerProperty layerProperty ) +{ + QgsDebugMsg( "entering." ); + mTableModel.addTableEntry( layerProperty ); +} + +QgsOracleSourceSelect::~QgsOracleSourceSelect() +{ + if ( mColumnTypeThread ) + { + mColumnTypeThread->stop(); + mColumnTypeThread->wait(); + finishList(); + } + + QSettings settings; + settings.setValue( "/Windows/OracleSourceSelect/geometry", saveGeometry() ); + + for ( int i = 0; i < mTableModel.columnCount(); i++ ) + { + settings.setValue( QString( "/Windows/OracleSourceSelect/columnWidths/%1" ).arg( i ), mTablesTreeView->columnWidth( i ) ); + } +} + +void QgsOracleSourceSelect::populateConnectionList() +{ + cmbConnections->blockSignals( true ); + cmbConnections->clear(); + cmbConnections->addItems( QgsOracleConn::connectionList() ); + cmbConnections->blockSignals( false ); + + setConnectionListPosition(); + + btnEdit->setDisabled( cmbConnections->count() == 0 ); + btnDelete->setDisabled( cmbConnections->count() == 0 ); + btnConnect->setDisabled( cmbConnections->count() == 0 ); + cmbConnections->setDisabled( cmbConnections->count() == 0 ); +} + +// Slot for performing action when the Add button is clicked +void QgsOracleSourceSelect::addTables() +{ + mSelectedTables.clear(); + + foreach ( QModelIndex idx, mTablesTreeView->selectionModel()->selection().indexes() ) + { + if ( idx.column() != QgsOracleTableModel::dbtmTable ) + continue; + + QString uri = mTableModel.layerURI( mProxyModel.mapToSource( idx ), mConnInfo, mUseEstimatedMetadata ); + if ( uri.isNull() ) + continue; + + mSelectedTables << uri; + } + + if ( mSelectedTables.empty() ) + { + QMessageBox::information( this, tr( "Select Table" ), tr( "You must select a table in order to add a layer." ) ); + } + else + { + emit addDatabaseLayers( mSelectedTables, "oracle" ); + accept(); + } +} + +void QgsOracleSourceSelect::on_btnConnect_clicked() +{ + cbxAllowGeometrylessTables->setEnabled( true ); + + if ( mColumnTypeThread ) + { + mColumnTypeThread->stop(); + return; + } + + QModelIndex rootItemIndex = mTableModel.indexFromItem( mTableModel.invisibleRootItem() ); + mTableModel.removeRows( 0, mTableModel.rowCount( rootItemIndex ), rootItemIndex ); + + // populate the table list + QgsDataSourceURI uri = QgsOracleConn::connUri( cmbConnections->currentText() ); + + QgsDebugMsg( "Connection info: " + uri.connectionInfo() ); + + mConnInfo = uri.connectionInfo(); + mUseEstimatedMetadata = uri.useEstimatedMetadata(); + + QgsOracleConn *conn = QgsOracleConn::connectDb( uri.connectionInfo() ); + if ( conn ) + { + QApplication::setOverrideCursor( Qt::WaitCursor ); + + mIsConnected = true; + mTablesTreeDelegate->setConn( QgsOracleConn::connectDb( uri.connectionInfo() ) ); + + bool userTablesOnly = QgsOracleConn::userTablesOnly( cmbConnections->currentText() ); + bool allowGeometrylessTables = cbxAllowGeometrylessTables->isChecked(); + + QVector layers; + if ( conn->supportedLayers( layers, userTablesOnly, allowGeometrylessTables ) ) + { + // Add the supported layers to the table + foreach ( QgsOracleLayerProperty layer, layers ) + { + if ( !layer.geometryColName.isNull() ) + { + if ( layer.types.contains( QGis::WKBUnknown ) || layer.srids.isEmpty() ) + { + addSearchGeometryColumn( layer ); + } + } + else + { + QgsDebugMsg( QString( "adding table %1.%2" ).arg( layer.ownerName ).arg( layer.tableName ) ); + layer.types.clear(); + layer.srids.clear(); + mTableModel.addTableEntry( layer ); + } + } + + if ( mColumnTypeThread ) + { + btnConnect->setText( tr( "Stop" ) ); + mColumnTypeThread->start(); + } + } + + //if we have only one owner item, expand it by default + int numTopLevelItems = mTableModel.invisibleRootItem()->rowCount(); + if ( numTopLevelItems < 2 || mTableModel.tableCount() < 20 ) + { + //expand all the toplevel items + for ( int i = 0; i < numTopLevelItems; ++i ) + { + mTablesTreeView->expand( mProxyModel.mapFromSource( mTableModel.indexFromItem( mTableModel.invisibleRootItem()->child( i ) ) ) ); + } + } + + conn->disconnect(); + + if ( !mColumnTypeThread ) + { + finishList(); + } + } + else + { + // Let user know we couldn't initialise the Oracle provider + QMessageBox::warning( this, + tr( "Oracle Locator Provider" ), + tr( "Could not open the Oracle Locator Provider" ) ); + } +} + +void QgsOracleSourceSelect::finishList() +{ + QApplication::restoreOverrideCursor(); + + if ( cmbConnections->count() > 0 ) + mAddButton->setEnabled( true ); + +#if 0 + for ( int i = 0; i < QgsOracleTableModel::dbtmColumns; i++ ) + mTablesTreeView->resizeColumnToContents( i ); +#endif + + mTablesTreeView->sortByColumn( QgsOracleTableModel::dbtmTable, Qt::AscendingOrder ); + mTablesTreeView->sortByColumn( QgsOracleTableModel::dbtmOwner, Qt::AscendingOrder ); + +} + +void QgsOracleSourceSelect::columnThreadFinished() +{ + delete mColumnTypeThread; + mColumnTypeThread = 0; + btnConnect->setText( tr( "Connect" ) ); + + finishList(); +} + +QStringList QgsOracleSourceSelect::selectedTables() +{ + return mSelectedTables; +} + +QString QgsOracleSourceSelect::connectionInfo() +{ + return mConnInfo; +} + +void QgsOracleSourceSelect::setSql( const QModelIndex &index ) +{ + if ( !index.parent().isValid() ) + { + QgsDebugMsg( "no owner item found" ); + return; + } + + QModelIndex idx = mProxyModel.mapToSource( index ); + QString tableName = mTableModel.itemFromIndex( idx.sibling( idx.row(), QgsOracleTableModel::dbtmTable ) )->text(); + + QString uri = mTableModel.layerURI( idx, mConnInfo, mUseEstimatedMetadata ); + if ( uri.isNull() ) + { + QgsDebugMsg( "no uri" ); + return; + } + + QgsVectorLayer *vlayer = new QgsVectorLayer( uri, tableName, "oracle" ); + if ( !vlayer->isValid() ) + { + delete vlayer; + return; + } + + // create a query builder object + QgsQueryBuilder *gb = new QgsQueryBuilder( vlayer, this ); + if ( gb->exec() ) + { + mTableModel.setSql( mProxyModel.mapToSource( index ), gb->sql() ); + } + + delete gb; + delete vlayer; +} + +void QgsOracleSourceSelect::addSearchGeometryColumn( QgsOracleLayerProperty layerProperty ) +{ + // store the column details and do the query in a thread + if ( !mColumnTypeThread ) + { + QgsOracleConn *conn = QgsOracleConn::connectDb( mConnInfo ); + if ( conn ) + { + + mColumnTypeThread = new QgsOracleColumnTypeThread( conn, mUseEstimatedMetadata ); + + connect( mColumnTypeThread, SIGNAL( setLayerType( QgsOracleLayerProperty ) ), + this, SLOT( setLayerType( QgsOracleLayerProperty ) ) ); + connect( this, SIGNAL( addGeometryColumn( QgsOracleLayerProperty ) ), + mColumnTypeThread, SLOT( addGeometryColumn( QgsOracleLayerProperty ) ) ); + connect( mColumnTypeThread, SIGNAL( finished() ), + this, SLOT( columnThreadFinished() ) ); + } + } + + emit addGeometryColumn( layerProperty ); +} + +QString QgsOracleSourceSelect::fullDescription( QString owner, QString table, QString column, QString type ) +{ + QString full_desc = ""; + if ( !owner.isEmpty() ) + full_desc = QgsOracleConn::quotedIdentifier( owner ) + "."; + full_desc += QgsOracleConn::quotedIdentifier( table ) + " (" + column + ") " + type; + return full_desc; +} + +void QgsOracleSourceSelect::setConnectionListPosition() +{ + // If possible, set the item currently displayed database + QString toSelect = QgsOracleConn::selectedConnection(); + + cmbConnections->setCurrentIndex( cmbConnections->findText( toSelect ) ); + + if ( cmbConnections->currentIndex() < 0 ) + { + if ( toSelect.isNull() ) + cmbConnections->setCurrentIndex( 0 ); + else + cmbConnections->setCurrentIndex( cmbConnections->count() - 1 ); + } +} + +void QgsOracleSourceSelect::setSearchExpression( const QString& regexp ) +{ + Q_UNUSED( regexp ); +} diff --git a/src/providers/oracle/qgsoraclesourceselect.h b/src/providers/oracle/qgsoraclesourceselect.h new file mode 100644 index 000000000000..f6342b712c55 --- /dev/null +++ b/src/providers/oracle/qgsoraclesourceselect.h @@ -0,0 +1,167 @@ +/*************************************************************************** + qgoraclesourceselect.h - description + ------------------- + begin : August 2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSORACLESOURCESELECT_H +#define QGSORACLESOURCESELECT_H + +#include "ui_qgsdbsourceselectbase.h" +#include "qgisgui.h" +#include "qgsdbfilterproxymodel.h" +#include "qgscontexthelp.h" + +#include "qgsoracletablemodel.h" + +#include +#include +#include +#include + +class QPushButton; +class QStringList; +class QgisApp; +class QgsOracleColumnTypeThread; +class QgsOracleSourceSelect; + +class QgsOracleSourceSelectDelegate : public QItemDelegate +{ + Q_OBJECT; + + public: + QgsOracleSourceSelectDelegate( QObject *parent = NULL ) + : QItemDelegate( parent ) + , mConn( 0 ) + {} + + ~QgsOracleSourceSelectDelegate() + { + setConn( 0 ); + } + + QWidget *createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const; + void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const; + + void setConn( QgsOracleConn *conn ) { if ( mConn ) mConn->disconnect(); mConn = conn; } + private: + QgsOracleConn *mConn; +}; + + +/*! \class QgsOracleSourceSelect + * \brief Dialog to create connections and add tables from Oracle. + * + * This dialog allows the user to define and save connection information + * for Oracle databases. The user can then connect and add + * tables from the database to the map canvas. + */ +class QgsOracleSourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase +{ + Q_OBJECT + + public: + //! Constructor + QgsOracleSourceSelect( QWidget *parent = 0, Qt::WFlags fl = QgisGui::ModalDialogFlags, bool managerMode = false, bool embeddedMode = false ); + //! Destructor + ~QgsOracleSourceSelect(); + //! Populate the connection list combo box + void populateConnectionList(); + //! String list containing the selected tables + QStringList selectedTables(); + //! Connection info (database, host, user, password) + QString connectionInfo(); + + signals: + void addDatabaseLayers( QStringList const & layerPathList, QString const & providerKey ); + void connectionsChanged(); + void addGeometryColumn( QgsOracleLayerProperty ); + + public slots: + //! Determines the tables the user selected and closes the dialog + void addTables(); + void buildQuery(); + + /*! Connects to the database using the stored connection parameters. + * Once connected, available layers are displayed. + */ + void on_btnConnect_clicked(); + void on_cbxAllowGeometrylessTables_stateChanged( int ); + //! Opens the create connection dialog to build a new connection + void on_btnNew_clicked(); + //! Opens a dialog to edit an existing connection + void on_btnEdit_clicked(); + //! Deletes the selected connection + void on_btnDelete_clicked(); + //! Saves the selected connections to file + void on_btnSave_clicked(); + //! Loads the selected connections from file + void on_btnLoad_clicked(); + void on_mSearchTableEdit_textChanged( const QString & text ); + void on_mSearchColumnComboBox_currentIndexChanged( const QString & text ); + void on_mSearchModeComboBox_currentIndexChanged( const QString & text ); + void on_cmbConnections_currentIndexChanged( const QString &text ); + void setSql( const QModelIndex& index ); + //! Store the selected database + void setLayerType( QgsOracleLayerProperty layerProperty ); + void on_mTablesTreeView_clicked( const QModelIndex &index ); + void on_mTablesTreeView_doubleClicked( const QModelIndex &index ); + //!Sets a new regular expression to the model + void setSearchExpression( const QString& regexp ); + + void on_buttonBox_helpRequested() { QgsContextHelp::run( metaObject()->className() ); } + + void columnThreadFinished(); + + private: + typedef QPair geomPair; + typedef QList geomCol; + + //! Connections manager mode + bool mManagerMode; + + //! Embedded mode, without 'Close' + bool mEmbeddedMode; + + // queue another query for the thread + void addSearchGeometryColumn( QgsOracleLayerProperty layerProperty ); + + // Set the position of the database connection list to the last + // used one. + void setConnectionListPosition(); + // Combine the schema, table and column data into a single string + // useful for display to the user + QString fullDescription( QString schema, QString table, QString column, QString type ); + // The column labels + QStringList mColumnLabels; + // Our thread for doing long running queries + QgsOracleColumnTypeThread *mColumnTypeThread; + QString mConnInfo; + QStringList mSelectedTables; + bool mUseEstimatedMetadata; + // Storage for the range of layer type icons + QMap > mLayerIcons; + + //! Model that acts as datasource for mTableTreeWidget + QgsOracleTableModel mTableModel; + QgsDbFilterProxyModel mProxyModel; + QgsOracleSourceSelectDelegate *mTablesTreeDelegate; + + QPushButton *mBuildQueryButton; + QPushButton *mAddButton; + + void finishList(); + bool mIsConnected; +}; + +#endif // QGSORACLESOURCESELECT_H diff --git a/src/providers/oracle/qgsoracletablemodel.cpp b/src/providers/oracle/qgsoracletablemodel.cpp new file mode 100644 index 000000000000..aa802b897384 --- /dev/null +++ b/src/providers/oracle/qgsoracletablemodel.cpp @@ -0,0 +1,339 @@ +/*************************************************************************** + qgsoracletablemodel.cpp - description + ------------------- + begin : August 2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsoracletablemodel.h" +#include "qgsdataitem.h" +#include "qgslogger.h" + +QgsOracleTableModel::QgsOracleTableModel() + : QStandardItemModel() + , mTableCount( 0 ) +{ + QStringList headerLabels; + headerLabels << tr( "Owner" ); + headerLabels << tr( "Table" ); + headerLabels << tr( "Type" ); + headerLabels << tr( "Geometry column" ); + headerLabels << tr( "SRID" ); + headerLabels << tr( "Primary key column" ); + headerLabels << tr( "Select at id" ); + headerLabels << tr( "Sql" ); + setHorizontalHeaderLabels( headerLabels ); +} + +QgsOracleTableModel::~QgsOracleTableModel() +{ +} + +void QgsOracleTableModel::addTableEntry( QgsOracleLayerProperty layerProperty ) +{ +#ifdef QGISDEBUG + QString typeString; + foreach ( QGis::WkbType type, layerProperty.types ) + { + if ( !typeString.isEmpty() ) + typeString += "|"; + typeString += QString::number( type ); + } + QString sridString; + foreach ( int srid, layerProperty.srids ) + { + if ( !sridString.isEmpty() ) + sridString += "|"; + sridString += QString::number( srid ); + } + QgsDebugMsg( layerProperty.toString() ); +#endif + + if ( !layerProperty.types.contains( QGis::WKBUnknown ) ) + { + layerProperty.types << ( layerProperty.geometryColName.isEmpty() ? QGis::WKBNoGeometry : QGis::WKBUnknown ); + layerProperty.srids << 0; + } + + // is there already a root item with the given scheme Name? + QStandardItem *ownerItem = 0; + + for ( int i = 0; i < layerProperty.size(); i++ ) + { + QGis::WkbType wkbType = layerProperty.at( i ).types[0]; + int srid = layerProperty.at( i ).srids[0]; + + QStandardItem *ownerNameItem = new QStandardItem( layerProperty.ownerName ); + + bool selectable = wkbType != QGis::WKBUnknown && srid != 0; + + QStandardItem *typeItem = new QStandardItem( iconForWkbType( wkbType ), wkbType == QGis::WKBUnknown ? tr( "Select..." ) : QgsOracleConn::displayStringForWkbType( wkbType ) ); + typeItem->setData( wkbType == QGis::WKBUnknown, Qt::UserRole + 1 ); + typeItem->setData( wkbType, Qt::UserRole + 2 ); + if ( wkbType == QGis::WKBUnknown ) + typeItem->setFlags( typeItem->flags() | Qt::ItemIsEditable ); + + QStandardItem *tableItem = new QStandardItem( layerProperty.tableName ); + QStandardItem *geomItem = new QStandardItem( layerProperty.geometryColName ); + QStandardItem *sridItem = new QStandardItem( QString::number( srid ) ); + sridItem->setEditable( srid == 0 ); + if ( srid == 0 ) + { + sridItem->setText( tr( "Enter..." ) ); + sridItem->setFlags( sridItem->flags() | Qt::ItemIsEditable ); + } + + QStandardItem *pkItem = new QStandardItem( layerProperty.isView ? tr( "Select..." ) : "" ); + if ( layerProperty.isView ) + pkItem->setFlags( pkItem->flags() | Qt::ItemIsEditable ); + else + pkItem->setFlags( pkItem->flags() & ~Qt::ItemIsEditable ); + + pkItem->setData( layerProperty.isView, Qt::UserRole + 1 ); + pkItem->setData( false, Qt::UserRole + 2 ); // not selected + + QStandardItem *selItem = new QStandardItem( "" ); + selItem->setFlags( selItem->flags() | Qt::ItemIsUserCheckable ); + selItem->setCheckState( Qt::Checked ); + selItem->setToolTip( tr( "Disable 'Fast Access to Features at ID' capability to force keeping the attribute table in memory (e.g. in case of expensive views)." ) ); + + QStandardItem* sqlItem = new QStandardItem( layerProperty.sql ); + + QList childItemList; + childItemList << ownerNameItem; + childItemList << tableItem; + childItemList << typeItem; + childItemList << geomItem; + childItemList << sridItem; + childItemList << pkItem; + childItemList << selItem; + childItemList << sqlItem; + + foreach ( QStandardItem *item, childItemList ) + { + if ( selectable ) + { + item->setFlags( item->flags() | Qt::ItemIsSelectable ); + } + else + { + item->setFlags( item->flags() & ~Qt::ItemIsSelectable ); + } + } + + if ( !ownerItem ) + { + QList ownerItems = findItems( layerProperty.ownerName, Qt::MatchExactly, dbtmOwner ); + + // there is already an item for this schema + if ( ownerItems.size() > 0 ) + { + ownerItem = ownerItems.at( dbtmOwner ); + } + else + { + // create a new toplevel item for this schema + ownerItem = new QStandardItem( layerProperty.ownerName ); + ownerItem->setFlags( Qt::ItemIsEnabled ); + invisibleRootItem()->setChild( invisibleRootItem()->rowCount(), ownerItem ); + } + } + + ownerItem->appendRow( childItemList ); + + ++mTableCount; + } +} + +void QgsOracleTableModel::setSql( const QModelIndex &index, const QString &sql ) +{ + if ( !index.isValid() || !index.parent().isValid() ) + { + return; + } + + //find out schema name and table name + QModelIndex ownerSibling = index.sibling( index.row(), dbtmOwner ); + QModelIndex tableSibling = index.sibling( index.row(), dbtmTable ); + QModelIndex geomSibling = index.sibling( index.row(), dbtmGeomCol ); + + if ( !ownerSibling.isValid() || !tableSibling.isValid() || !geomSibling.isValid() ) + { + return; + } + + QString ownerName = itemFromIndex( ownerSibling )->text(); + QString tableName = itemFromIndex( tableSibling )->text(); + QString geomName = itemFromIndex( geomSibling )->text(); + + QList ownerItems = findItems( ownerName, Qt::MatchExactly, dbtmOwner ); + if ( ownerItems.size() < 1 ) + { + return; + } + + QStandardItem* ownerItem = ownerItems.at( dbtmOwner ); + + int n = ownerItem->rowCount(); + for ( int i = 0; i < n; i++ ) + { + QModelIndex currentChildIndex = indexFromItem( ownerItem->child( i, dbtmOwner ) ); + if ( !currentChildIndex.isValid() ) + { + continue; + } + + QModelIndex currentTableIndex = currentChildIndex.sibling( i, dbtmTable ); + if ( !currentTableIndex.isValid() ) + { + continue; + } + + QModelIndex currentGeomIndex = currentChildIndex.sibling( i, dbtmGeomCol ); + if ( !currentGeomIndex.isValid() ) + { + continue; + } + + if ( itemFromIndex( currentTableIndex )->text() == tableName && itemFromIndex( currentGeomIndex )->text() == geomName ) + { + QModelIndex sqlIndex = currentChildIndex.sibling( i, dbtmSql ); + if ( sqlIndex.isValid() ) + { + itemFromIndex( sqlIndex )->setText( sql ); + break; + } + } + } +} + +QIcon QgsOracleTableModel::iconForWkbType( QGis::WkbType type ) +{ + switch ( type ) + { + case QGis::WKBPoint: + case QGis::WKBPoint25D: + case QGis::WKBMultiPoint: + case QGis::WKBMultiPoint25D: + return QgsApplication::getThemeIcon( "/mIconPointLayer.png" ); + case QGis::WKBLineString: + case QGis::WKBLineString25D: + case QGis::WKBMultiLineString: + case QGis::WKBMultiLineString25D: + return QgsApplication::getThemeIcon( "/mIconLineLayer.png" ); + case QGis::WKBPolygon: + case QGis::WKBPolygon25D: + case QGis::WKBMultiPolygon: + case QGis::WKBMultiPolygon25D: + return QgsApplication::getThemeIcon( "/mIconPolygonLayer.png" ); + case QGis::WKBNoGeometry: + return QgsApplication::getThemeIcon( "/mIconTableLayer.png" ); + case QGis::WKBUnknown: + break; + } + return QgsApplication::getThemeIcon( "/mIconLayer.png" ); +} + +bool QgsOracleTableModel::setData( const QModelIndex &idx, const QVariant &value, int role ) +{ + if ( !QStandardItemModel::setData( idx, value, role ) ) + return false; + + if ( idx.column() == dbtmType || idx.column() == dbtmSrid || idx.column() == dbtmPkCol ) + { + QGis::WkbType geomType = ( QGis::WkbType ) idx.sibling( idx.row(), dbtmType ).data( Qt::UserRole + 2 ).toInt(); + + bool ok = geomType != QGis::WKBUnknown; + + if ( ok && geomType != QGis::WKBNoGeometry ) + idx.sibling( idx.row(), dbtmSrid ).data().toInt( &ok ); + + int srid = idx.sibling( idx.row(), dbtmSrid ).data().toInt(); + ok &= srid != 0; + + ok &= !idx.sibling( idx.row(), dbtmPkCol ).data( Qt::UserRole + 1 ).toBool() || + idx.sibling( idx.row(), dbtmPkCol ).data( Qt::UserRole + 2 ).toBool(); + + for ( int i = 0; i < dbtmColumns; i++ ) + { + QStandardItem *item = itemFromIndex( idx.sibling( idx.row(), i ) ); + if ( ok ) + item->setFlags( item->flags() | Qt::ItemIsSelectable ); + else + item->setFlags( item->flags() & ~Qt::ItemIsSelectable ); + } + } + + return true; +} + +QString QgsOracleTableModel::layerURI( const QModelIndex &index, QString connInfo, bool useEstimatedMetadata ) +{ + if ( !index.isValid() ) + { + QgsDebugMsg( "invalid index" ); + return QString::null; + } + + QGis::WkbType wkbType = ( QGis::WkbType ) itemFromIndex( index.sibling( index.row(), dbtmType ) )->data( Qt::UserRole + 2 ).toInt(); + if ( wkbType == QGis::WKBUnknown ) + { + QgsDebugMsg( "unknown geometry type" ); + // no geometry type selected + return QString::null; + } + + QStandardItem *pkItem = itemFromIndex( index.sibling( index.row(), dbtmPkCol ) ); + QString pkColumnName = pkItem->data( Qt::DisplayRole ).toString(); + bool isView = pkItem->data( Qt::UserRole + 1 ).toBool(); + bool isSet = pkItem->data( Qt::UserRole + 2 ).toBool(); + + if ( isView && !isSet ) + { + // no valid primary candidate selected + QgsDebugMsg( "no pk candidate selected" ); + return QString::null; + } + + QString ownerName = index.sibling( index.row(), dbtmOwner ).data( Qt::DisplayRole ).toString(); + QString tableName = index.sibling( index.row(), dbtmTable ).data( Qt::DisplayRole ).toString(); + + QString geomColumnName; + QString srid; + if ( wkbType != QGis::WKBNoGeometry ) + { + geomColumnName = index.sibling( index.row(), dbtmGeomCol ).data( Qt::DisplayRole ).toString(); + + srid = index.sibling( index.row(), dbtmSrid ).data( Qt::DisplayRole ).toString(); + bool ok; + srid.toInt( &ok ); + if ( !ok ) + { + QgsDebugMsg( "srid not numeric" ); + return QString::null; + } + } + + bool selectAtId = itemFromIndex( index.sibling( index.row(), dbtmSelectAtId ) )->checkState() == Qt::Checked; + QString sql = index.sibling( index.row(), dbtmSql ).data( Qt::DisplayRole ).toString(); + + QgsDataSourceURI uri( connInfo ); + uri.setDataSource( ownerName, tableName, geomColumnName, sql, pkColumnName ); + uri.setUseEstimatedMetadata( useEstimatedMetadata ); + uri.setWkbType( wkbType ); + uri.setSrid( srid ); + uri.disableSelectAtId( !selectAtId ); + + QgsDebugMsg( QString( "returning uri %1" ).arg( uri.uri() ) ); + return uri.uri(); +} diff --git a/src/providers/oracle/qgsoracletablemodel.h b/src/providers/oracle/qgsoracletablemodel.h new file mode 100644 index 000000000000..731174f9eeed --- /dev/null +++ b/src/providers/oracle/qgsoracletablemodel.h @@ -0,0 +1,69 @@ +/*************************************************************************** + qgsoracletablemodel.h - description + ------------------- + begin : August 2012 + copyright : (C) 2012 by Juergen E. Fischer + email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSORACLETABLEMODEL_H +#define QGSORACLETABLEMODEL_H +#include + +#include "qgis.h" +#include "qgsoracleconn.h" + +class QIcon; + +/**A model that holds the tables of a database in a hierarchy where the +schemas are the root elements that contain the individual tables as children. +The tables have the following columns: Type, Owner, Tablename, Geometry Column, Sql*/ +class QgsOracleTableModel : public QStandardItemModel +{ + Q_OBJECT + public: + QgsOracleTableModel(); + ~QgsOracleTableModel(); + + /**Adds entry for one database table to the model*/ + void addTableEntry( QgsOracleLayerProperty property ); + + /**Sets an sql statement that belongs to a cell specified by a model index*/ + void setSql( const QModelIndex& index, const QString& sql ); + + /**Returns the number of tables in the model*/ + int tableCount() const { return mTableCount; } + + enum columns + { + dbtmOwner = 0, + dbtmTable, + dbtmType, + dbtmGeomCol, + dbtmSrid, + dbtmPkCol, + dbtmSelectAtId, + dbtmSql, + dbtmColumns + }; + + bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ); + + QString layerURI( const QModelIndex &index, QString connInfo, bool useEstimatedMetadata ); + + static QIcon iconForWkbType( QGis::WkbType type ); + + private: + /**Number of tables in the model*/ + int mTableCount; +}; + +#endif // QGSORACLETABLEMODEL_H diff --git a/src/providers/postgres/qgspgnewconnection.cpp b/src/providers/postgres/qgspgnewconnection.cpp index 3be45d118fde..e241ffdb83d1 100644 --- a/src/providers/postgres/qgspgnewconnection.cpp +++ b/src/providers/postgres/qgspgnewconnection.cpp @@ -51,7 +51,7 @@ QgsPgNewConnection::QgsPgNewConnection( QWidget *parent, const QString& connName txtPort->setText( port ); txtDatabase->setText( settings.value( key + "/database" ).toString() ); cb_publicSchemaOnly->setChecked( settings.value( key + "/publicOnly", false ).toBool() ); - cb_geometryColumnsOnly->setChecked( settings.value( key + "/geometrycolumnsOnly", true ).toBool() ); + cb_geometryColumnsOnly->setChecked( settings.value( key + "/geometryColumnsOnly", true ).toBool() ); cb_allowGeometrylessTables->setChecked( settings.value( key + "/allowGeometrylessTables", false ).toBool() ); // Ensure that cb_publicSchemaOnly is set correctly on_cb_geometryColumnsOnly_clicked(); diff --git a/src/providers/postgres/qgspostgresconn.cpp b/src/providers/postgres/qgspostgresconn.cpp index fb9ad2f5ec36..54e069e49a0b 100644 --- a/src/providers/postgres/qgspostgresconn.cpp +++ b/src/providers/postgres/qgspostgresconn.cpp @@ -1091,7 +1091,7 @@ void QgsPostgresConn::retrieveLayerTypes( QgsPostgresLayerProperty &layerPropert } - // it is possible that the where clause restricts the feature type or srid + // our estimatation ignores that a where clause might restrict the feature type or srid if ( useEstimatedMetadata ) { table = QString( "(SELECT %1 FROM %2 WHERE %1 IS NOT NULL%3 LIMIT %4) AS t" ) @@ -1476,7 +1476,7 @@ bool QgsPostgresConn::geometryColumnsOnly( QString theConnName ) { QSettings settings; - return settings.value( "/PostgreSQL/connections/" + theConnName + "/geometrycolumnsOnly", false ).toBool(); + return settings.value( "/PostgreSQL/connections/" + theConnName + "/geometryColumnsOnly", false ).toBool(); } bool QgsPostgresConn::allowGeometrylessTables( QString theConnName ) diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index 4822846a8aeb..9c4a6a7959b3 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -445,7 +445,6 @@ bool QgsPostgresProvider::getFeature( QgsPostgresResult &queryResult, int row, b else { feature.setGeometryAndOwnership( 0, 0 ); - QgsMessageLog::logMessage( tr( "Couldn't get the feature geometry in binary form" ), tr( "PostGIS" ) ); } col++; @@ -1861,12 +1860,12 @@ bool QgsPostgresProvider::getTopoLayerInfo( ) void QgsPostgresProvider::dropOrphanedTopoGeoms( ) { QString sql = QString( "DELETE FROM %1.relation WHERE layer_id = %2 AND " - "topogeo_id NOT IN ( SELECT id(%3) FROM %4.%5 )" ) - .arg( quotedIdentifier(mTopoLayerInfo.topologyName) ) + "topogeo_id NOT IN ( SELECT id(%3) FROM %4.%5 )" ) + .arg( quotedIdentifier( mTopoLayerInfo.topologyName ) ) .arg( mTopoLayerInfo.layerId ) - .arg( quotedIdentifier(mGeometryColumn) ) - .arg( quotedIdentifier(mSchemaName) ) - .arg( quotedIdentifier(mTableName) ) + .arg( quotedIdentifier( mGeometryColumn ) ) + .arg( quotedIdentifier( mSchemaName ) ) + .arg( quotedIdentifier( mTableName ) ) ; QgsDebugMsg( "TopoGeom orphans cleanup query: " + sql ); @@ -3163,7 +3162,7 @@ bool QgsPostgresProvider::getGeometryDetails() mEnabledCapabilities &= ~( QgsVectorDataProvider::ChangeGeometries | QgsVectorDataProvider::AddFeatures ); } - QgsDebugMsg( QString( "Feature type name is %1" ).arg( QGis::qgisFeatureTypes[ geometryType()] ) ); + QgsDebugMsg( QString( "Feature type name is %1" ).arg( QGis::featureType( geometryType() ) ) ); QgsDebugMsg( QString( "Spatial column type is %1" ).arg( QgsPostgresConn::displayStringForGeomType( mSpatialColType ) ) ); return mValid; diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui index 5e70fc5b5426..d8fb08bb1883 100644 --- a/src/ui/qgisapp.ui +++ b/src/ui/qgisapp.ui @@ -265,6 +265,7 @@ + @@ -1188,6 +1189,18 @@ Ctrl+Shift+M + + + + :/images/themes/default/mActionAddOracleLayer.png:/images/themes/default/mActionAddOracleLayer.png + + + Add Oracle Spatial Layer... + + + Ctrl+Shift+O + + diff --git a/src/ui/qgsoraclenewconnectionbase.ui b/src/ui/qgsoraclenewconnectionbase.ui new file mode 100644 index 000000000000..2f9989c17e3f --- /dev/null +++ b/src/ui/qgsoraclenewconnectionbase.ui @@ -0,0 +1,265 @@ + + + QgsOracleNewConnectionBase + + + + 0 + 0 + 400 + 459 + + + + + 0 + 0 + + + + Create a New Oracle connection + + + true + + + true + + + + 9 + + + 6 + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok + + + + + + + Connection Information + + + + + + + + + Password + + + txtPassword + + + + + + + QLineEdit::Password + + + + + + + Save Username + + + + + + + Username + + + txtUsername + + + + + + + Name of the new connection + + + + + + + Database + + + txtDatabase + + + + + + + Name + + + txtName + + + + + + + Restrict the displayed tables to those that are in the geometry_columns table + + + <html><head/><body><p>Restricts the displayed tables to those that are in the all_sdo_geom_metadata view. This can speed up the initial display of spatial tables.</p></body></html> + + + Only look in meta data table + + + + + + + Restrict the search to the public schema for spatial tables not in the geometry_columns table + + + <html><head/><body><p>When searching for spatial tables restrict the search to tables that are owner by the user.</p></body></html> + + + Only look for user's tables + + + + + + + Also list tables with no geometry + + + false + + + + + + + Port + + + txtPort + + + + + + + 1521 + + + + + + + &Test Connect + + + + + + + Save Password + + + + + + + + + + Host + + + txtHost + + + + + + + + + + Use estimated table statistics for the layer metadata. + + + <html><head/><body><p>When the layer is setup various metadata is required for the Oracle table. This includes information such as the table row count, geometry type and spatial extents of the data in the geometry column. If the table contains a large number of rows determining this metadata is time consuming.</p><p>By activating this option the following fast table metadata operations are done:</p><p>1) Row count is determined from all_tables.num_rows.</p><p>2) Table extents are always determined with the SDO_TUNE.EXTENTS_OF function even if a layer filter is applied.</p><p>3) The table geometry is determined from the first 100 non-null geometry rows in the table.</p></body></html> + + + Use estimated table metadata + + + + + + + + + + + txtName + txtDatabase + txtHost + txtPort + txtUsername + txtPassword + chkStoreUsername + chkStorePassword + btnConnect + cb_geometryColumnsOnly + cb_userTablesOnly + cb_allowGeometrylessTables + cb_useEstimatedMetadata + buttonBox + + + + + buttonBox + rejected() + QgsOracleNewConnectionBase + reject() + + + 313 + 501 + + + 451 + 312 + + + + + buttonBox + accepted() + QgsOracleNewConnectionBase + accept() + + + 395 + 501 + + + 450 + 287 + + + + + diff --git a/tests/src/core/testqgsgeometry.cpp b/tests/src/core/testqgsgeometry.cpp index 43af2879c90e..7ddd39bdf9a1 100644 --- a/tests/src/core/testqgsgeometry.cpp +++ b/tests/src/core/testqgsgeometry.cpp @@ -58,8 +58,6 @@ class TestQgsGeometry: public QObject private: /** A helper method to do a render check to see if the geometry op is as expected */ bool renderCheck( QString theTestName, QString theComment = "" ); - /** A helper method to return wkb geometry type as a string */ - QString wkbTypeAsString( QGis::WkbType theType ); /** A helper method to dump to qdebug the geometry of a multipolygon */ void dumpMultiPolygon( QgsMultiPolygon &theMultiPolygon ); /** A helper method to dump to qdebug the geometry of a polygon */ @@ -217,7 +215,7 @@ void TestQgsGeometry::simplifyCheck1() QVERIFY( mpPolylineGeometryD->simplify( 0.5 ) ); // should be a single polygon as A intersect B QgsGeometry * mypSimplifyGeometry = mpPolylineGeometryD->simplify( 0.5 ); - qDebug( "Geometry Type: %s", wkbTypeAsString( mypSimplifyGeometry->wkbType() ).toLocal8Bit().constData() ); + qDebug( "Geometry Type: %s", QGis::featureType( mypSimplifyGeometry->wkbType() ) ); QVERIFY( mypSimplifyGeometry->wkbType() == QGis::WKBLineString ); QgsPolyline myLine = mypSimplifyGeometry->asPolyline(); QVERIFY( myLine.size() > 0 ); //check that the union created a feature @@ -230,7 +228,7 @@ void TestQgsGeometry::intersectionCheck1() QVERIFY( mpPolygonGeometryA->intersects( mpPolygonGeometryB ) ); // should be a single polygon as A intersect B QgsGeometry * mypIntersectionGeometry = mpPolygonGeometryA->intersection( mpPolygonGeometryB ); - qDebug( "Geometry Type: %s", wkbTypeAsString( mypIntersectionGeometry->wkbType() ).toLocal8Bit().constData() ); + qDebug( "Geometry Type: %s", QGis::featureType( mypIntersectionGeometry->wkbType() ) ); QVERIFY( mypIntersectionGeometry->wkbType() == QGis::WKBPolygon ); QgsPolygon myPolygon = mypIntersectionGeometry->asPolygon(); QVERIFY( myPolygon.size() > 0 ); //check that the union created a feature @@ -247,7 +245,7 @@ void TestQgsGeometry::unionCheck1() { // should be a multipolygon with 2 parts as A does not intersect C QgsGeometry * mypUnionGeometry = mpPolygonGeometryA->combine( mpPolygonGeometryC ); - qDebug( "Geometry Type: %s", wkbTypeAsString( mypUnionGeometry->wkbType() ).toLocal8Bit().constData() ); + qDebug( "Geometry Type: %s", QGis::featureType( mypUnionGeometry->wkbType() ) ); QVERIFY( mypUnionGeometry->wkbType() == QGis::WKBMultiPolygon ); QgsMultiPolygon myMultiPolygon = mypUnionGeometry->asMultiPolygon(); QVERIFY( myMultiPolygon.size() > 0 ); //check that the union did not fail @@ -260,7 +258,7 @@ void TestQgsGeometry::unionCheck2() { // should be a single polygon as A intersect B QgsGeometry * mypUnionGeometry = mpPolygonGeometryA->combine( mpPolygonGeometryB ); - qDebug( "Geometry Type: %s", wkbTypeAsString( mypUnionGeometry->wkbType() ).toLocal8Bit().constData() ); + qDebug( "Geometry Type: %s", QGis::featureType( mypUnionGeometry->wkbType() ) ); QVERIFY( mypUnionGeometry->wkbType() == QGis::WKBPolygon ); QgsPolygon myPolygon = mypUnionGeometry->asPolygon(); QVERIFY( myPolygon.size() > 0 ); //check that the union created a feature @@ -273,7 +271,7 @@ void TestQgsGeometry::differenceCheck1() { // should be same as A since A does not intersect C so diff is 100% of A QgsGeometry * mypDifferenceGeometry = mpPolygonGeometryA->difference( mpPolygonGeometryC ); - qDebug( "Geometry Type: %s", wkbTypeAsString( mypDifferenceGeometry->wkbType() ).toLocal8Bit().constData() ); + qDebug( "Geometry Type: %s", QGis::featureType( mypDifferenceGeometry->wkbType() ) ); QVERIFY( mypDifferenceGeometry->wkbType() == QGis::WKBPolygon ); QgsPolygon myPolygon = mypDifferenceGeometry->asPolygon(); QVERIFY( myPolygon.size() > 0 ); //check that the union did not fail @@ -286,7 +284,7 @@ void TestQgsGeometry::differenceCheck2() { // should be a single polygon as (A - B) = subset of A QgsGeometry * mypDifferenceGeometry = mpPolygonGeometryA->difference( mpPolygonGeometryB ); - qDebug( "Geometry Type: %s", wkbTypeAsString( mypDifferenceGeometry->wkbType() ).toLocal8Bit().constData() ); + qDebug( "Geometry Type: %s", QGis::featureType( mypDifferenceGeometry->wkbType() ) ); QVERIFY( mypDifferenceGeometry->wkbType() == QGis::WKBPolygon ); QgsPolygon myPolygon = mypDifferenceGeometry->asPolygon(); QVERIFY( myPolygon.size() > 0 ); //check that the union created a feature @@ -298,7 +296,7 @@ void TestQgsGeometry::bufferCheck() { // should be a single polygon QgsGeometry * mypBufferGeometry = mpPolygonGeometryB->buffer( 10, 10 ); - qDebug( "Geometry Type: %s", wkbTypeAsString( mypBufferGeometry->wkbType() ).toLocal8Bit().constData() ); + qDebug( "Geometry Type: %s", QGis::featureType( mypBufferGeometry->wkbType() ) ); QVERIFY( mypBufferGeometry->wkbType() == QGis::WKBPolygon ); QgsPolygon myPolygon = mypBufferGeometry->asPolygon(); QVERIFY( myPolygon.size() > 0 ); //check that the buffer created a feature @@ -371,41 +369,6 @@ void TestQgsGeometry::dumpPolyline( QgsPolyline &thePolyline ) mpPainter->drawPolyline( myPoints ); } -QString TestQgsGeometry::wkbTypeAsString( QGis::WkbType theType ) -{ - switch ( theType ) - { - case QGis::WKBPoint: - return "WKBPoint"; - case QGis::WKBLineString: - return "WKBLineString"; - case QGis::WKBPolygon: - return "WKBPolygon"; - case QGis::WKBMultiPoint: - return "WKBMultiPoint"; - case QGis::WKBMultiLineString: - return "WKBMultiLineString"; - case QGis::WKBMultiPolygon: - return "WKBMultiPolygon"; - case QGis::WKBUnknown: - return "WKBUnknown"; - case QGis::WKBPoint25D: - return "WKBPoint25D"; - case QGis::WKBLineString25D: - return "WKBLineString25D"; - case QGis::WKBPolygon25D: - return "WKBPolygon25D"; - case QGis::WKBMultiPoint25D: - return "WKBMultiPoint25D"; - case QGis::WKBMultiLineString25D: - return "WKBMultiLineString25D"; - case QGis::WKBMultiPolygon25D: - return "WKBMultiPolygon25D"; - default: - return "Unknown type"; - } -} - QTEST_MAIN( TestQgsGeometry ) #include "moc_testqgsgeometry.cxx"