From d212ca7f7174a2063f245e31e27f2848387c1df0 Mon Sep 17 00:00:00 2001 From: szekerest Date: Mon, 6 Feb 2012 09:24:17 +0100 Subject: [PATCH 1/2] Adding the native MSSQL provider --- CMakeLists.txt | 3 + cmake_templates/qgsconfig.h.in | 2 + doc/CONTRIBUTORS | 1 + images/images.qrc | 2 + .../themes/default/mActionAddMssqlLayer.png | Bin 0 -> 4218 bytes images/themes/gis/mIconMssql.png | Bin 0 -> 3321 bytes src/app/qgisapp.cpp | 34 + src/app/qgisapp.h | 4 + src/gui/qgsmanageconnectionsdialog.cpp | 149 ++ src/gui/qgsmanageconnectionsdialog.h | 3 + src/providers/CMakeLists.txt | 1 + src/providers/mssql/CMakeLists.txt | 35 + src/providers/mssql/qgsmssqldataitems.cpp | 555 ++++++ src/providers/mssql/qgsmssqldataitems.h | 117 ++ .../mssql/qgsmssqlgeometryparser.cpp | 562 ++++++ src/providers/mssql/qgsmssqlnewconnection.cpp | 176 ++ src/providers/mssql/qgsmssqlnewconnection.h | 45 + src/providers/mssql/qgsmssqlprovider.cpp | 1541 +++++++++++++++++ src/providers/mssql/qgsmssqlprovider.h | 348 ++++ src/providers/mssql/qgsmssqlsourceselect.cpp | 814 +++++++++ src/providers/mssql/qgsmssqlsourceselect.h | 189 ++ src/providers/mssql/qgsmssqltablemodel.cpp | 431 +++++ src/providers/mssql/qgsmssqltablemodel.h | 89 + src/ui/qgisapp.ui | 16 +- src/ui/qgsmssqlnewconnectionbase.ui | 295 ++++ 25 files changed, 5411 insertions(+), 1 deletion(-) create mode 100644 images/themes/default/mActionAddMssqlLayer.png create mode 100644 images/themes/gis/mIconMssql.png create mode 100644 src/providers/mssql/CMakeLists.txt create mode 100644 src/providers/mssql/qgsmssqldataitems.cpp create mode 100644 src/providers/mssql/qgsmssqldataitems.h create mode 100644 src/providers/mssql/qgsmssqlgeometryparser.cpp create mode 100644 src/providers/mssql/qgsmssqlnewconnection.cpp create mode 100644 src/providers/mssql/qgsmssqlnewconnection.h create mode 100644 src/providers/mssql/qgsmssqlprovider.cpp create mode 100644 src/providers/mssql/qgsmssqlprovider.h create mode 100644 src/providers/mssql/qgsmssqlsourceselect.cpp create mode 100644 src/providers/mssql/qgsmssqlsourceselect.h create mode 100644 src/providers/mssql/qgsmssqltablemodel.cpp create mode 100644 src/providers/mssql/qgsmssqltablemodel.h create mode 100644 src/ui/qgsmssqlnewconnectionbase.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index ec4b9794b28b..4d4ea9991951 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,9 @@ IF (SPATIALITE_FOUND) SET (HAVE_SPATIALITE TRUE) ENDIF (SPATIALITE_FOUND) +# following variable is used in qgsconfig.h +SET (HAVE_MSSQL TRUE) + ############################################################# # search for Qt4 SET(QT_MIN_VERSION 4.4.0) diff --git a/cmake_templates/qgsconfig.h.in b/cmake_templates/qgsconfig.h.in index 25568cd7aaa2..1b50ca54a82e 100644 --- a/cmake_templates/qgsconfig.h.in +++ b/cmake_templates/qgsconfig.h.in @@ -31,6 +31,8 @@ #cmakedefine HAVE_SPATIALITE +#cmakedefine HAVE_MSSQL + #cmakedefine HAVE_PYTHON #endif diff --git a/doc/CONTRIBUTORS b/doc/CONTRIBUTORS index 812c73fbdd23..c81b48587fe6 100644 --- a/doc/CONTRIBUTORS +++ b/doc/CONTRIBUTORS @@ -49,6 +49,7 @@ Richard Kostecky Robert Szczepanek Stefanie Tellex Steven Mizuno +Tamas Szekeres Tom Russo Tyler Mitchell Vita Cizek diff --git a/images/images.qrc b/images/images.qrc index ee2f748558a7..0f4c29b569c3 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -363,8 +363,10 @@ themes/gis/mIconSpatialite.png themes/gis/mIconRaster.png themes/gis/mIconPostgis.png + themes/gis/mIconMssql.png themes/gis/mIconConnect.png themes/gis/mIconDbSchema.png + themes/default/mActionAddMssqlLayer.png qgis_tips/symbol_levels.png diff --git a/images/themes/default/mActionAddMssqlLayer.png b/images/themes/default/mActionAddMssqlLayer.png new file mode 100644 index 0000000000000000000000000000000000000000..6b216fac2d9de8db9ffb19f4fd74dff02ecfaec7 GIT binary patch literal 4218 zcmV-=5QXoFP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000H0Nkl}E>*{qT0%TiT~yVNh)D8&gYOoRv21USwnujm>=JB;VggUKUdPOY zl>WTrXGf>2zU+sA!|w~Ys`g1JmkRfGlX*BpDzCT@Yy>!uIuAgp?x_z4TzlmT@l5q^ zz)&M_w;ga1Ing@UPWIs(>6C|9$!FSlaNrO)WiBo)B$bzj~}{}+5M^1(kl;4$eRkejDo|=1RWPkRUdz$=0 z9+NfJV)hs@SZ|361&~S91A6m&d6mwN(+O({y(Bt<9SlKfl(Wi;Gn2b&_g~svc>mc3&JJkfB>fus3}V z@l2KU&J17v^-;tr7(ES#OAR)e}1>R#|F)DodmgVK4J{_Uie#^x@m>aLBa+q%fR zj(kCXaPG5oM_pS+?krfNoGqW{i_tfUTuj&=;?j=lWQFR&O0hh>u;|Nv3Ap!GN2&uz z%JVGyWzLjO6B2NDdJkE5$1NGi*fcLzp2Z8y6lZw0>yN&4`owWneOyGUx5J_X`1A{3eGqzmXt)a*mjnZt5mei6TJ`dycxOPOymeMK9CvM7Z3ul z5YID{Os!s(1VjZR1|tRmmbF5sEihErj}?Q7mT5+L|Gz-qb+h6NQGxELb4#^DsgH0E$*z(nsvG Q4gdfE07*qoM6N<$f<;pNL;wH) literal 0 HcmV?d00001 diff --git a/images/themes/gis/mIconMssql.png b/images/themes/gis/mIconMssql.png new file mode 100644 index 0000000000000000000000000000000000000000..e8aa104df4d30d145946b9b2c8449ea87e5ccab7 GIT binary patch literal 3321 zcmVKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0006cNklzfV(97(MsC_xkR8eejB}hDIc|i<2=VBu*xVMXd{ygNvJN3=$VN zHwR4|nm90;2>(F|Q`1BiVj3k81BxwbO{F}L(jWKzy4OJ=#XcPLOkciy-^sb>yN5_A z@jp=iY)sv`pAA%bnFyI9l4Jou5?Pa!RJD+-l@_1X`sc}!f@^nYa|w+VIQMU9dLlA) zLs%9g0MKr8+4Xuf@O*`_M9~lUZ23i@dO|@ub!O8@>X^CyRgPF(1zH1g_uGnizLdF9 zuQxUUXeS7=8Liz5V841lN-P#4lWOCf<0BHlH&;isY2#Ca(Z=^3qEN*h@PPYB%}u-A z^15z*-eK6PI|w6S?L!laBEbv~_D}+Ix5LhpMWka*G)*RMPD%hA01!nUzU+D!HYI>A z02fq61hC^B`nMA)ivD}K&vl5?&p2Py&HRK5=d1#qCP3*g{y<99T@&xNZImm41$5#G z@>ll#;;O^;4k?5Hk_@P91n_(xgaBqTz|{%ETVCmyu>|93Gn~nD2qEA{9IckX`O#tI zGCszfE;P~uRS_(&u+x7c08mQ7s0ro(Qp?)Ar^LS;)#D->eHioCQP(bC&7basetIcon( getThemeIcon( "/mActionNewVectorLayer.png" ) ); mActionAddSpatiaLiteLayer->setIcon( getThemeIcon( "/mActionAddSpatiaLiteLayer.png" ) ); +#endif +#ifdef HAVE_MSSQL + mActionAddMssqlLayer->setIcon( getThemeIcon( "/mActionAddMssqlLayer.png" ) ); #endif mActionRemoveLayer->setIcon( getThemeIcon( "/mActionRemoveLayer.png" ) ); mActionSetLayerCRS->setIcon( getThemeIcon( "/mActionSetLayerCRS.png" ) ); @@ -2515,6 +2524,31 @@ void QgisApp::addSpatiaLiteLayer() } // QgisApp::addSpatiaLiteLayer() #endif +#ifndef HAVE_MSSQL +void QgisApp::addMssqlLayer() {} +#else +void QgisApp::addMssqlLayer() +{ + if ( mMapCanvas && mMapCanvas->isDrawing() ) + { + return; + } + + // show the MS SQL dialog + QDialog *dbs = dynamic_cast( QgsProviderRegistry::instance()->selectWidget( QString( "mssql" ), this ) ); + if ( !dbs ) + { + QMessageBox::warning( this, tr( "MSSQL" ), tr( "Cannot get MS SQL select dialog from provider." ) ); + return; + } + connect( dbs , SIGNAL( addDatabaseLayers( QStringList const &, QString const & ) ), + this , SLOT( addDatabaseLayers( QStringList const &, QString const & ) ) ); + dbs->exec(); + delete dbs; + +} // QgisApp::addMssqlLayer() +#endif + void QgisApp::addWmsLayer() { if ( mMapCanvas && mMapCanvas->isDrawing() ) diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index 8b540964ffb0..46659f696763 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -502,6 +502,10 @@ class QgisApp : public QMainWindow, private Ui::MainWindow //! Add a SpatiaLite layer to the map void addSpatiaLiteLayer(); //#endif + //#ifdef HAVE_MSSQL + //! Add a SpatiaLite layer to the map + void addMssqlLayer(); + //#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/gui/qgsmanageconnectionsdialog.cpp b/src/gui/qgsmanageconnectionsdialog.cpp index 62b1925097cd..f437b866aa57 100644 --- a/src/gui/qgsmanageconnectionsdialog.cpp +++ b/src/gui/qgsmanageconnectionsdialog.cpp @@ -108,6 +108,9 @@ void QgsManageConnectionsDialog::doExportImport() case PostGIS: doc = savePgConnections( items ); break; + case MSSQL: + doc = saveMssqlConnections( items ); + break; } QFile file( mFileName ); @@ -161,6 +164,9 @@ void QgsManageConnectionsDialog::doExportImport() case PostGIS: loadPgConnections( doc, items ); break; + case MSSQL: + loadMssqlConnections( doc, items ); + break; } // clear connections list and close window listConnections->clear(); @@ -187,6 +193,9 @@ bool QgsManageConnectionsDialog::populateConnections() case PostGIS: settings.beginGroup( "/PostgreSQL/connections" ); break; + case MSSQL: + settings.beginGroup( "/MSSQL/connections" ); + break; } QStringList keys = settings.childGroups(); QStringList::Iterator it = keys.begin(); @@ -256,6 +265,15 @@ bool QgsManageConnectionsDialog::populateConnections() return false; } break; + + case MSSQL: + if ( root.tagName() != "qgsMssqlConnections" ) + { + QMessageBox::information( this, tr( "Loading connections" ), + tr( "The file is not an MSSQL connections exchange file." ) ); + return false; + } + break; } QDomElement child = root.firstChildElement(); @@ -363,6 +381,47 @@ QDomDocument QgsManageConnectionsDialog::savePgConnections( const QStringList &c return doc; } +QDomDocument QgsManageConnectionsDialog::saveMssqlConnections( const QStringList &connections ) +{ + QDomDocument doc( "connections" ); + QDomElement root = doc.createElement( "qgsMssqlConnections" ); + root.setAttribute( "version", "1.0" ); + doc.appendChild( root ); + + QSettings settings; + QString path; + for ( int i = 0; i < connections.count(); ++i ) + { + path = "/MSSQL/connections/" + connections[ i ]; + QDomElement el = doc.createElement( "mssql" ); + 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( "service", settings.value( path + "/service", "" ).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::loadWMSConnections( const QDomDocument &doc, const QStringList &items ) { QDomElement root = doc.documentElement(); @@ -616,6 +675,96 @@ void QgsManageConnectionsDialog::loadPgConnections( const QDomDocument &doc, con } } +void QgsManageConnectionsDialog::loadMssqlConnections( const QDomDocument &doc, const QStringList &items ) +{ + QDomElement root = doc.documentElement(); + if ( root.tagName() != "qgsMssqlConnections" ) + { + QMessageBox::information( this, + tr( "Loading connections" ), + tr( "The file is not an PostGIS connections exchange file." ) ); + return; + } + + QString connectionName; + QSettings settings; + settings.beginGroup( "/MSSQL/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( "/MSSQL/connections/" + connectionName ); + + settings.setValue( "/host", child.attribute( "host" ) ); + settings.setValue( "/port", child.attribute( "port" ) ); + settings.setValue( "/database", child.attribute( "database" ) ); + if ( child.hasAttribute( "service" ) ) + { + settings.setValue( "/service", child.attribute( "service" ) ); + } + else + { + settings.setValue( "/service", "" ); + } + 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 09284681a49e..c59f48a20753 100644 --- a/src/gui/qgsmanageconnectionsdialog.h +++ b/src/gui/qgsmanageconnectionsdialog.h @@ -38,6 +38,7 @@ class GUI_EXPORT QgsManageConnectionsDialog : public QDialog, private Ui::QgsMan WMS, PostGIS, WFS, + MSSQL, }; // constructor @@ -55,9 +56,11 @@ class GUI_EXPORT QgsManageConnectionsDialog : public QDialog, private Ui::QgsMan QDomDocument saveWMSConnections( const QStringList &connections ); QDomDocument saveWFSConnections( const QStringList &connections ); QDomDocument savePgConnections( const QStringList & connections ); + QDomDocument saveMssqlConnections( const QStringList & connections ); void loadWMSConnections( const QDomDocument &doc, const QStringList &items ); void loadWFSConnections( const QDomDocument &doc, const QStringList &items ); void loadPgConnections( const QDomDocument &doc, const QStringList &items ); + void loadMssqlConnections( const QDomDocument &doc, const QStringList &items ); QString mFileName; Mode mDialogMode; diff --git a/src/providers/CMakeLists.txt b/src/providers/CMakeLists.txt index e373d075bc02..b5ac3e34c3d2 100644 --- a/src/providers/CMakeLists.txt +++ b/src/providers/CMakeLists.txt @@ -9,6 +9,7 @@ ADD_SUBDIRECTORY(delimitedtext) ADD_SUBDIRECTORY(osm) ADD_SUBDIRECTORY(sqlanywhere) ADD_SUBDIRECTORY(gdal) +ADD_SUBDIRECTORY(mssql) IF (POSTGRES_FOUND) ADD_SUBDIRECTORY(postgres) diff --git a/src/providers/mssql/CMakeLists.txt b/src/providers/mssql/CMakeLists.txt new file mode 100644 index 000000000000..213c63b379af --- /dev/null +++ b/src/providers/mssql/CMakeLists.txt @@ -0,0 +1,35 @@ + +SET (MSSQL_SRCS qgsmssqlprovider.cpp qgsmssqlgeometryparser.cpp qgsmssqlsourceselect.cpp qgsmssqltablemodel.cpp qgsmssqlnewconnection.cpp qgsmssqldataitems.cpp) + +SET (MSSQL_MOC_HDRS qgsmssqlprovider.h qgsmssqlsourceselect.h qgsmssqltablemodel.h qgsmssqlnewconnection.h qgsmssqldataitems.h) + +######################################################## +# Build + +QT4_WRAP_CPP(MSSQL_MOC_SRCS ${MSSQL_MOC_HDRS}) + +INCLUDE_DIRECTORIES( + . + ../../core + ${GDAL_INCLUDE_DIR} + ${GEOS_INCLUDE_DIR} + ${QT_INCLUDE_DIR} + ../../gui + ${CMAKE_CURRENT_BINARY_DIR}/../../ui +) + + +ADD_LIBRARY(mssqlprovider MODULE ${MSSQL_SRCS} ${MSSQL_MOC_SRCS}) + +TARGET_LINK_LIBRARIES(mssqlprovider + qgis_core + qgis_gui + ${QT_QTSQL_LIBRARY} +) +######################################################## +# Install + +INSTALL (TARGETS mssqlprovider + RUNTIME DESTINATION ${QGIS_PLUGIN_DIR} + LIBRARY DESTINATION ${QGIS_PLUGIN_DIR}) + diff --git a/src/providers/mssql/qgsmssqldataitems.cpp b/src/providers/mssql/qgsmssqldataitems.cpp new file mode 100644 index 000000000000..ee9c0e51308f --- /dev/null +++ b/src/providers/mssql/qgsmssqldataitems.cpp @@ -0,0 +1,555 @@ +/*************************************************************************** + qgsmssqldataitems.cpp - description + ------------------- + begin : 2011-10-08 + copyright : (C) 2011 by Tamas Szekeres + email : szekerest at gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "qgsmssqldataitems.h" + +#include "qgsmssqlsourceselect.h" +#include "qgsmssqlnewconnection.h" +#include "qgslogger.h" +#include "qgsmimedatautils.h" +#include "qgsvectorlayerimport.h" +#include "qgsdatasourceuri.h" +#include "qgsmssqlprovider.h" + +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +QgsMssqlConnectionItem::QgsMssqlConnectionItem( QgsDataItem* parent, QString name, QString path ) + : QgsDataCollectionItem( parent, name, path ) +{ + mIcon = QIcon( getThemePixmap( "mIconConnect.png" ) ); + QSettings settings; + QString key = "/MSSQL/connections/" + mName; + mService = settings.value( key + "/service" ).toString(); + mHost = settings.value( key + "/host" ).toString(); + mDatabase = settings.value( key + "/database" ).toString(); + mUsername; + mPassword; + if ( settings.value( key + "/saveUsername" ).toString() == "true" ) + { + mUsername = settings.value( key + "/username" ).toString(); + } + + if ( settings.value( key + "/savePassword" ).toString() == "true" ) + { + mPassword = settings.value( key + "/password" ).toString(); + } + + mUseGeometryColumns = settings.value( key + "/geometryColumns", false ).toBool(); + + mConnInfo = "dbname='" + mDatabase + "' host=" + mHost + " user='" + mUsername + "' password='" + mPassword + "'"; + if (!mService.isEmpty()) + mConnInfo += " service='" + mService + "'"; +} + +QgsMssqlConnectionItem::~QgsMssqlConnectionItem() +{ +} + +void QgsMssqlConnectionItem::refresh() +{ + QgsDebugMsg( "mPath = " + mPath ); + + // read up the schemas and layers from database + QVector items = createChildren( ); + + // Add new items + foreach( QgsDataItem *item, items ) + { + // Is it present in childs? + int index = findItem( mChildren, item ); + if ( index >= 0 ) + { + ((QgsMssqlSchemaItem*)mChildren[index])->addLayers(item); + delete item; + continue; + } + addChildItem( item, true ); + } +} + +QVector QgsMssqlConnectionItem::createChildren() +{ + QgsDebugMsg( "Entered" ); + + QVector children; + + QSqlDatabase db = QgsMssqlProvider::GetDatabase( mService, + mHost, mDatabase, mUsername, mPassword ); + + if ( !QgsMssqlProvider::OpenDatabase( db ) ) + { + children.append( new QgsErrorItem( this, db.lastError( ).text( ), mPath + "/error" ) ); + return children; + } + + QString connectionName; + if (mService.isEmpty()) + { + if (mHost.isEmpty()) + { + children.append( new QgsErrorItem( this, "QgsMssqlProvider host name not specified", mPath + "/error" ) ); + return children; + } + + if (mDatabase.isEmpty()) + { + children.append( new QgsErrorItem( this, "QgsMssqlProvider database name not specified", mPath + "/error" ) ); + return children; + } + connectionName = mHost + "." + mDatabase; + } + else + connectionName = mService; + + QgsMssqlGeomColumnTypeThread *columnTypeThread = 0; + + // build sql statement + QString query("select "); + if (mUseGeometryColumns) + { + query += "f_table_schema, f_table_name, f_geometry_column, srid, geometry_type from geometry_columns"; + } + else + { + query += "sys.schemas.name, sys.objects.name, sys.columns.name, null, 'GEOMETRY' from sys.columns join sys.types on sys.columns.system_type_id = sys.types.system_type_id and sys.columns.user_type_id = sys.types.user_type_id join sys.objects on sys.objects.object_id = sys.columns.object_id join sys.schemas on sys.objects.schema_id = sys.schemas.schema_id where sys.types.name = 'geometry' or sys.types.name = 'geography' and sys.objects.type = 'U'"; + } + + // issue the sql query + QSqlQuery q = QSqlQuery(db); + q.setForwardOnly(true); + q.exec( query ); + + if (q.isActive()) + { + QVector newLayers; + while ( q.next() ) + { + QgsMssqlLayerProperty layer; + layer.schemaName = q.value(0).toString(); + layer.tableName = q.value(1).toString(); + layer.geometryColName = q.value(2).toString(); + layer.srid = q.value(3).toString(); + layer.type = q.value(4).toString(); + layer.pkCols = QStringList(); //TODO + + // skip layers which are added already + bool skip = false; + foreach( QgsDataItem *child, mChildren ) + { + if ( child->name() == layer.schemaName) + { + foreach( QgsDataItem *child2, child->children() ) + { + if ( child2->name() == layer.tableName) + { + newLayers.append(child2); + skip = true; // already added + break; + } + } + } + } + + if (skip) + continue; + + QString type = layer.type; + QString srid = layer.srid; + + QgsMssqlSchemaItem* schemaItem = NULL; + foreach( QgsDataItem *child, children ) + { + if ( child->name() == layer.schemaName ) + { + schemaItem = (QgsMssqlSchemaItem*)child; + break; + } + } + + if (!schemaItem) + { + schemaItem = new QgsMssqlSchemaItem( this, layer.schemaName, mPath + "/" + layer.schemaName ); + children.append( schemaItem ); + } + + if ( !layer.geometryColName.isNull() ) + { + if ( type == "GEOMETRY" || type.isNull() || srid.isEmpty() ) + { + if ( !columnTypeThread ) + { + columnTypeThread = new QgsMssqlGeomColumnTypeThread( + connectionName, true /* use estimated metadata */ ); + + connect( columnTypeThread, SIGNAL( setLayerType( QgsMssqlLayerProperty ) ), + this, SLOT( setLayerType( QgsMssqlLayerProperty ) ) ); + connect( this, SIGNAL( addGeometryColumn( QgsMssqlLayerProperty ) ), + columnTypeThread, SLOT( addGeometryColumn( QgsMssqlLayerProperty ) ) ); + } + + emit addGeometryColumn( layer ); + continue; + } + } + + QgsMssqlLayerItem* added = schemaItem->addLayer( layer, false ); + if (added) + newLayers.append(added); + } + + // Remove no more present items + foreach( QgsDataItem *child, mChildren ) + { + foreach( QgsDataItem *child2, child->children() ) + { + if ( findItem( newLayers, child2 ) < 0 ) + child->deleteChildItem( child2 ); + } + } + + // spawn threads (new layers will be added later on) + if ( columnTypeThread ) + columnTypeThread->start(); + } + + return children; +} + +void QgsMssqlConnectionItem::setLayerType( QgsMssqlLayerProperty layerProperty ) +{ + QgsMssqlSchemaItem *schemaItem = NULL; + + foreach( QgsDataItem *child, mChildren ) + { + if ( child->name() == layerProperty.schemaName) + { + schemaItem = (QgsMssqlSchemaItem*)child; + break; + } + } + + foreach( QgsDataItem *layerItem, schemaItem->children() ) + { + if ( layerItem->name() == layerProperty.tableName) + return; // already added + } + + if ( !schemaItem ) + { + QgsDebugMsg( QString( "schema item for %1 not found." ).arg( layerProperty.schemaName ) ); + return; + } + + QStringList typeList = layerProperty.type.split( ",", QString::SkipEmptyParts ); + QStringList sridList = layerProperty.srid.split( ",", QString::SkipEmptyParts ); + Q_ASSERT( typeList.size() == sridList.size() ); + + for ( int i = 0 ; i < typeList.size(); i++ ) + { + QGis::GeometryType geomType = QgsMssqlTableModel::geomTypeFromMssql( typeList[i] ); + if ( geomType == QGis::UnknownGeometry ) + { + QgsDebugMsg( QString( "unsupported geometry type:%1" ).arg( typeList[i] ) ); + continue; + } + + layerProperty.type = typeList[i]; + layerProperty.srid = sridList[i]; + schemaItem->addLayer( layerProperty, true ); + } +} + +bool QgsMssqlConnectionItem::equal( const QgsDataItem *other ) +{ + if ( type() != other->type() ) + { + return false; + } + + const QgsMssqlConnectionItem *o = qobject_cast( other ); + return ( mPath == o->mPath && mName == o->mName ); +} + +QList QgsMssqlConnectionItem::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 QgsMssqlConnectionItem::editConnection() +{ + QgsMssqlNewConnection nc( NULL, mName ); + if ( nc.exec() ) + { + // the parent should be updated + mParent->refresh(); + } +} + +void QgsMssqlConnectionItem::deleteConnection() +{ + QgsMssqlSourceSelect::deleteConnection( mName ); + // the parent should be updated + mParent->refresh(); +} + +bool QgsMssqlConnectionItem::handleDrop( const QMimeData * data, Qt::DropAction ) +{ + if ( !QgsMimeDataUtils::isUriList( data ) ) + return false; + + // TODO: probably should show a GUI with settings etc + + qApp->setOverrideCursor( Qt::WaitCursor ); + + 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() ) + { + QString uri = connInfo() + " table=" + u.name + " (qgs_geometry)"; + + QgsVectorLayerImport::ImportError err; + QString importError; + err = QgsVectorLayerImport::importLayer( srcLayer, uri, "mssql", &srcLayer->crs(), false, &importError ); + 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; + } + + qApp->restoreOverrideCursor(); + + if ( hasError ) + { + QMessageBox::warning( 0, tr( "Import to MSSQL database" ), tr( "Failed to import some layers!\n\n" ) + importResults.join( "\n" ) ); + } + else + { + QMessageBox::information( 0, tr( "Import to MSSQL database" ), tr( "Import was successful." ) ); + } + + if (mPopulated) + refresh(); + else + populate(); + + return true; +} + + +// --------------------------------------------------------------------------- +QgsMssqlLayerItem::QgsMssqlLayerItem( QgsDataItem* parent, QString name, QString path, QgsLayerItem::LayerType layerType, QgsMssqlLayerProperty layerProperty ) + : QgsLayerItem( parent, name, path, QString(), layerType, "mssql" ) + , mLayerProperty( layerProperty ) +{ + mUri = createUri(); + mPopulated = true; +} + +QgsMssqlLayerItem::~QgsMssqlLayerItem() +{ +} + +QgsMssqlLayerItem* QgsMssqlLayerItem::createClone() +{ + return new QgsMssqlLayerItem(mParent, mName, mPath, mLayerType, mLayerProperty); +} + +QString QgsMssqlLayerItem::createUri() +{ + QString pkColName = mLayerProperty.pkCols.size() > 0 ? mLayerProperty.pkCols.at( 0 ) : QString::null; + QgsMssqlConnectionItem *connItem = qobject_cast( parent() ? parent()->parent() : 0 ); + + if ( !connItem ) + { + QgsDebugMsg( "connection item not found." ); + return QString::null; + } + + QgsDataSourceURI uri = QgsDataSourceURI( connItem->connInfo() ); + uri.setDataSource( mLayerProperty.schemaName, mLayerProperty.tableName, mLayerProperty.geometryColName, mLayerProperty.sql, pkColName ); + uri.setSrid( mLayerProperty.srid ); + uri.setGeometryType( QgsMssqlTableModel::geomTypeFromMssql( mLayerProperty.type ) ); + QgsDebugMsg( QString( "layer uri: %1" ).arg( uri.uri() ) ); + return uri.uri(); +} + +// --------------------------------------------------------------------------- +QgsMssqlSchemaItem::QgsMssqlSchemaItem( QgsDataItem* parent, QString name, QString path ) + : QgsDataCollectionItem( parent, name, path ) +{ + mIcon = QIcon( getThemePixmap( "mIconDbSchema.png" ) ); +} + +QVector QgsMssqlSchemaItem::createChildren() +{ + QgsDebugMsg( "Entering." ); + return QVector(); +} + +QgsMssqlSchemaItem::~QgsMssqlSchemaItem() +{ +} + +void QgsMssqlSchemaItem::addLayers(QgsDataItem* newLayers) +{ + // Add new items + foreach( QgsDataItem *child, newLayers->children() ) + { + // Is it present in childs? + if ( findItem( mChildren, child ) >= 0 ) + { + continue; + } + QgsMssqlLayerItem* layer = ((QgsMssqlLayerItem*)child)->createClone(); + addChildItem( layer, true ); + } +} + +QgsMssqlLayerItem* QgsMssqlSchemaItem::addLayer( QgsMssqlLayerProperty layerProperty, bool refresh ) +{ + QGis::GeometryType geomType = QgsMssqlTableModel::geomTypeFromMssql( layerProperty.type ); + QString tip = tr( "%1 as %2 in %3" ).arg( layerProperty.geometryColName ).arg( QgsMssqlTableModel::displayStringForGeomType( geomType ) ).arg( layerProperty.srid ); + + QgsLayerItem::LayerType layerType; + switch ( geomType ) + { + case QGis::Point: + layerType = QgsLayerItem::Point; + break; + case QGis::Line: + layerType = QgsLayerItem::Line; + break; + case QGis::Polygon: + layerType = QgsLayerItem::Polygon; + break; + default: + if ( layerProperty.type.isEmpty() && layerProperty.geometryColName.isEmpty() ) + { + layerType = QgsLayerItem::TableLayer; + tip = tr( "as geometryless table" ); + } + else + { + return NULL; + } + } + + QgsMssqlLayerItem *layerItem = new QgsMssqlLayerItem( this, layerProperty.tableName, mPath + "/" + layerProperty.tableName, layerType, layerProperty ); + layerItem->setToolTip( tip ); + if (refresh) + addChildItem( layerItem, true ); + else + addChild( layerItem ); + + return layerItem; +} + +// --------------------------------------------------------------------------- +QgsMssqlRootItem::QgsMssqlRootItem( QgsDataItem* parent, QString name, QString path ) + : QgsDataCollectionItem( parent, name, path ) +{ + mIcon = QIcon( getThemePixmap( "mIconMssql.png" ) ); + populate(); +} + +QgsMssqlRootItem::~QgsMssqlRootItem() +{ +} + +QVector QgsMssqlRootItem::createChildren() +{ + QVector connections; + QSettings settings; + settings.beginGroup( "/MSSQL/connections" ); + foreach( QString connName, settings.childGroups() ) + { + connections << new QgsMssqlConnectionItem( this, connName, mPath + "/" + connName ); + } + return connections; +} + +QList QgsMssqlRootItem::actions() +{ + QList lst; + + QAction* actionNew = new QAction( tr( "New..." ), this ); + connect( actionNew, SIGNAL( triggered() ), this, SLOT( newConnection() ) ); + lst.append( actionNew ); + + return lst; +} + +QWidget *QgsMssqlRootItem::paramWidget() +{ + QgsMssqlSourceSelect *select = new QgsMssqlSourceSelect( 0, 0, true, true ); + connect( select, SIGNAL( connectionsChanged() ), this, SLOT( connectionsChanged() ) ); + return select; +} + +void QgsMssqlRootItem::connectionsChanged() +{ + refresh(); +} + +void QgsMssqlRootItem::newConnection() +{ + QgsMssqlNewConnection nc( NULL ); + if ( nc.exec() ) + { + refresh(); + } +} diff --git a/src/providers/mssql/qgsmssqldataitems.h b/src/providers/mssql/qgsmssqldataitems.h new file mode 100644 index 000000000000..40aafe5c8aa6 --- /dev/null +++ b/src/providers/mssql/qgsmssqldataitems.h @@ -0,0 +1,117 @@ +/*************************************************************************** + qgsmssqldataitems.h - description + ------------------- + begin : 2011-10-08 + copyright : (C) 2011 by Tamas Szekeres + email : szekerest at gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 QGSMSSQLDATAITEMS_H +#define QGSMSSQLDATAITEMS_H + +#include "qgsdataitem.h" + +#include "qgsmssqlsourceselect.h" + +class QgsMssqlRootItem; +class QgsMssqlConnectionItem; +class QgsMssqlSchemaItem; +class QgsMssqlLayerItem; + +class QgsMssqlRootItem : public QgsDataCollectionItem +{ + Q_OBJECT + public: + QgsMssqlRootItem( QgsDataItem* parent, QString name, QString path ); + ~QgsMssqlRootItem(); + + QVector createChildren(); + + virtual QWidget * paramWidget(); + + virtual QList actions(); + + public slots: + void connectionsChanged(); + void newConnection(); +}; + +class QgsMssqlConnectionItem : public QgsDataCollectionItem +{ + Q_OBJECT + public: + QgsMssqlConnectionItem( QgsDataItem* parent, QString name, QString path ); + ~QgsMssqlConnectionItem(); + + 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 ); + void refresh(); + + QString connInfo() const { return mConnInfo; }; + + signals: + void addGeometryColumn( QgsMssqlLayerProperty ); + + public slots: + void editConnection(); + void deleteConnection(); + + void setLayerType( QgsMssqlLayerProperty layerProperty ); + + private: + QString mConnInfo; + QString mService; + QString mHost; + QString mDatabase; + QString mUsername; + QString mPassword; + bool mUseGeometryColumns; +}; + +class QgsMssqlSchemaItem : public QgsDataCollectionItem +{ + Q_OBJECT + public: + QgsMssqlSchemaItem( QgsDataItem* parent, QString name, QString path ); + ~QgsMssqlSchemaItem(); + + QVector createChildren(); + + QgsMssqlLayerItem* addLayer( QgsMssqlLayerProperty layerProperty, bool refresh ); + void refresh() {}; // do not refresh directly + void addLayers(QgsDataItem* newLayers); +}; + +class QgsMssqlLayerItem : public QgsLayerItem +{ + Q_OBJECT + + public: + QgsMssqlLayerItem( QgsDataItem* parent, QString name, QString path, QgsLayerItem::LayerType layerType, QgsMssqlLayerProperty layerProperties ); + ~QgsMssqlLayerItem(); + + QString createUri(); + + QgsMssqlLayerItem* createClone(); + bool Used; + + private: + QgsMssqlLayerProperty mLayerProperty; +}; + +#endif // QGSMSSQLDATAITEMS_H diff --git a/src/providers/mssql/qgsmssqlgeometryparser.cpp b/src/providers/mssql/qgsmssqlgeometryparser.cpp new file mode 100644 index 000000000000..35dbba2a0d53 --- /dev/null +++ b/src/providers/mssql/qgsmssqlgeometryparser.cpp @@ -0,0 +1,562 @@ +/*************************************************************************** + qgsmssqlgeometryparser.cpp - SqlGeometry parser for mssql server + ------------------- + begin : 2011-10-08 + copyright : (C) 2011 by Tamas Szekeres + email : szekerest at gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "qgsmssqlprovider.h" +#include "qgsgeometry.h" +#include "qgslogger.h" +#include "qgsapplication.h" + +/************************************************************************/ +/* Geometry parser macros */ +/************************************************************************/ + +#define SP_NONE 0 +#define SP_HASZVALUES 1 +#define SP_HASMVALUES 2 +#define SP_ISVALID 4 +#define SP_ISSINGLEPOINT 8 +#define SP_ISSINGLELINESEGMENT 0x10 +#define SP_ISWHOLEGLOBE 0x20 + +#define ST_UNKNOWN 0 +#define ST_POINT 1 +#define ST_LINESTRING 2 +#define ST_POLYGON 3 +#define ST_MULTIPOINT 4 +#define ST_MULTILINESTRING 5 +#define ST_MULTIPOLYGON 6 +#define ST_GEOMETRYCOLLECTION 7 + +#define ReadInt32(nPos) (*((unsigned int*)(pszData + (nPos)))) + +#define ReadByte(nPos) (pszData[nPos]) + +#define ReadDouble(nPos) (*((double*)(pszData + (nPos)))) + +#define ParentOffset(iShape) (ReadInt32(nShapePos + (iShape) * 9 )) +#define FigureOffset(iShape) (ReadInt32(nShapePos + (iShape) * 9 + 4)) +#define ShapeType(iShape) (ReadByte(nShapePos + (iShape) * 9 + 8)) + +#define NextFigureOffset(iShape) (iShape + 1 < nNumShapes? FigureOffset((iShape) +1) : nNumFigures) + +#define FigureAttribute(iFigure) (ReadByte(nFigurePos + (iFigure) * 5)) +#define PointOffset(iFigure) (ReadInt32(nFigurePos + (iFigure) * 5 + 1)) +#define NextPointOffset(iFigure) (iFigure + 1 < nNumFigures? PointOffset((iFigure) +1) : nNumPoints) + +#define ReadX(iPoint) (ReadDouble(nPointPos + nPointSize * (iPoint))) +#define ReadY(iPoint) (ReadDouble(nPointPos + nPointSize * (iPoint) + 8)) +#define ReadZ(iPoint) (ReadDouble(nPointPos + nPointSize * (iPoint) + 16)) + +/************************************************************************/ +/* QgsMssqlGeometryParser() */ +/************************************************************************/ + +QgsMssqlGeometryParser::QgsMssqlGeometryParser() +{ +} + +void QgsMssqlGeometryParser::DumpMemoryToLog(char* pszMsg, unsigned char* pszInput, int nLen) +{ + /*char buf[55]; + int len = 0; + QFile file( "qgsmssql.log" ); + file.open( QIODevice::Append ); + file.write( pszMsg, strlen(pszMsg) ); + file.write( "\n" ); + sprintf(buf + len, "%05d ", 0); + len += 6; + for ( int i = 0; i < nLen; i++) + { + sprintf(buf + len, "%02x ", pszInput[i]); + len += 3; + if (len == 54) + { + file.write( buf, len ); + len = 0; + file.write( "\n" ); + sprintf(buf + len, "%05d ", i + 1); + len = 6; + } + } + file.write( "\n" ); + file.close();*/ +} + +/************************************************************************/ +/* CopyBytes() */ +/************************************************************************/ + +void QgsMssqlGeometryParser::CopyBytes(void* src, int len) +{ + if (nWkbLen + len > nWkbMaxLen) + { + QgsDebugMsg( "CopyBytes wkb buffer realloc" ); + unsigned char* pszWkbTmp = new unsigned char[nWkbLen + len + 100]; + memcpy(pszWkbTmp, pszWkb, nWkbLen); + delete pszWkb; + pszWkb = pszWkbTmp; + nWkbMaxLen = nWkbLen + len + 100; + } + memcpy( pszWkb + nWkbLen, src, len ); + nWkbLen += len; +} + +/************************************************************************/ +/* CopyPoint() */ +/************************************************************************/ + +void QgsMssqlGeometryParser::CopyPoint(int iPoint) +{ + // copy byte order + CopyBytes( &chByteOrder, 1 ); + // copy type + int wkbType; + if ( chProps & SP_HASZVALUES ) + wkbType = QGis::WKBPoint25D; + else + wkbType = QGis::WKBPoint; + CopyBytes( &wkbType, 4 ); + // copy coordinates + CopyBytes(pszData + nPointPos + nPointSize * iPoint, nPointSize); +} + +/************************************************************************/ +/* ReadPoint() */ +/************************************************************************/ + +void QgsMssqlGeometryParser::ReadPoint(int iShape) +{ + int iFigure = FigureOffset(iShape); + if ( iFigure < nNumFigures ) + { + int iPoint = PointOffset(iFigure); + if ( iPoint < nNumPoints ) + { + CopyPoint(iPoint); + } + } +} + +/************************************************************************/ +/* ReadMultiPoint() */ +/************************************************************************/ + +void QgsMssqlGeometryParser::ReadMultiPoint(int iShape) +{ + int iFigure, iPoint, iNextPoint, iCount; + iFigure = FigureOffset(iShape); + iNextPoint = NextPointOffset(iFigure); + iCount = iNextPoint - PointOffset(iFigure); + if (iCount <= 0) + return; + // copy byte order + CopyBytes( &chByteOrder, 1 ); + // copy type + int wkbType; + if ( chProps & SP_HASZVALUES ) + wkbType = QGis::WKBMultiPoint25D; + else + wkbType = QGis::WKBMultiPoint; + CopyBytes( &wkbType, 4 ); + // copy point count + CopyBytes( &iCount, 4 ); + // copy points + for (iPoint = PointOffset(iFigure); iPoint < iNextPoint; iPoint++) + { + CopyPoint(iShape); + } +} + +/************************************************************************/ +/* ReadLineString() */ +/************************************************************************/ + +void QgsMssqlGeometryParser::ReadLineString(int iShape) +{ + int iFigure, iPoint, iNextPoint, i, iCount; + iFigure = FigureOffset(iShape); + + iPoint = PointOffset(iFigure); + iNextPoint = NextPointOffset(iFigure); + iCount = iNextPoint - iPoint; + if (iCount <= 0) + return; + // copy byte order + CopyBytes( &chByteOrder, 1 ); + // copy type + int wkbType; + if ( chProps & SP_HASZVALUES ) + wkbType = QGis::WKBLineString25D; + else + wkbType = QGis::WKBLineString; + CopyBytes( &wkbType, 4 ); + // copy length + CopyBytes( &iCount, 4 ); + // copy coordinates + i = 0; + while (iPoint < iNextPoint) + { + CopyBytes(pszData + nPointPos + nPointSize * iPoint, nPointSize); + ++iPoint; + ++i; + } +} + +/************************************************************************/ +/* ReadMultiLineString() */ +/************************************************************************/ + +void QgsMssqlGeometryParser::ReadMultiLineString(int iShape) +{ + int i, iCount; + iCount = nNumShapes - iShape - 1; + if (iCount <= 0) + return; + // copy byte order + CopyBytes( &chByteOrder, 1 ); + // copy type + int wkbType; + if ( chProps & SP_HASZVALUES ) + wkbType = QGis::WKBMultiLineString25D; + else + wkbType = QGis::WKBMultiLineString; + CopyBytes( &wkbType, 4 ); + // copy length + CopyBytes( &iCount, 4 ); + // copy linestrings + for (i = iShape + 1; i < nNumShapes; i++) + { + if (ParentOffset(i) == (unsigned int)iShape) + { + if ( ShapeType(i) == ST_LINESTRING ) + ReadLineString(i); + } + } +} + +/************************************************************************/ +/* ReadPolygon() */ +/************************************************************************/ + +void QgsMssqlGeometryParser::ReadPolygon(int iShape) +{ + int iFigure, iPoint, iNextPoint, iCount, i; + int iNextFigure = NextFigureOffset(iShape); + iCount = iNextFigure - FigureOffset(iShape); + if (iCount <= 0) + return; + // copy byte order + CopyBytes( &chByteOrder, 1 ); + // copy type + int wkbType; + if ( chProps & SP_HASZVALUES ) + wkbType = QGis::WKBPolygon25D; + else + wkbType = QGis::WKBPolygon; + CopyBytes( &wkbType, 4 ); + // copy ring count + CopyBytes( &iCount, 4 ); + // copy rings + for (iFigure = FigureOffset(iShape); iFigure < iNextFigure; iFigure++) + { + iPoint = PointOffset(iFigure); + iNextPoint = NextPointOffset(iFigure); + iCount = iNextPoint - iPoint; + if (iCount <= 0) + continue; + // copy point count + CopyBytes( &iCount, 4 ); + // copy coordinates + i = 0; + while (iPoint < iNextPoint) + { + CopyBytes(pszData + nPointPos + nPointSize * iPoint, nPointSize); + ++iPoint; + ++i; + } + } +} + +/************************************************************************/ +/* ReadMultiPolygon() */ +/************************************************************************/ + +void QgsMssqlGeometryParser::ReadMultiPolygon(int iShape) +{ + int i; + int iCount = nNumShapes - iShape - 1;; + if (iCount <= 0) + return; + // copy byte order + CopyBytes( &chByteOrder, 1 ); + // copy type + int wkbType; + if ( chProps & SP_HASZVALUES ) + wkbType = QGis::WKBMultiPolygon25D; + else + wkbType = QGis::WKBMultiPolygon; + CopyBytes( &wkbType, 4 ); + // copy poly count + CopyBytes( &iCount, 4 ); + // copy polygons + for (i = iShape + 1; i < nNumShapes; i++) + { + if (ParentOffset(i) == (unsigned int)iShape) + { + if ( ShapeType(i) == ST_POLYGON ) + ReadPolygon(i); + } + } +} + +/************************************************************************/ +/* ReadGeometryCollection() */ +/************************************************************************/ + +void QgsMssqlGeometryParser::ReadGeometryCollection(int iShape) +{ + int i; + int iCount = nNumShapes - iShape - 1;; + if (iCount <= 0) + return; + // copy byte order + CopyBytes( &chByteOrder, 1 ); + // copy type + int wkbType = QGis::WKBUnknown;; + CopyBytes( &wkbType, 4 ); + // copy geom count + CopyBytes( &iCount, 4 ); + for (i = iShape + 1; i < nNumShapes; i++) + { + if (ParentOffset(i) == (unsigned int)iShape) + { + switch (ShapeType(i)) + { + case ST_POINT: + ReadPoint(i); + break; + case ST_LINESTRING: + ReadLineString(i); + break; + case ST_POLYGON: + ReadPolygon(i); + break; + case ST_MULTIPOINT: + ReadMultiPoint(i); + break; + case ST_MULTILINESTRING: + ReadMultiLineString(i); + break; + case ST_MULTIPOLYGON: + ReadMultiPolygon(i); + break; + case ST_GEOMETRYCOLLECTION: + ReadGeometryCollection(i); + break; + } + } + } +} + +/************************************************************************/ +/* ParseSqlGeometry() */ +/************************************************************************/ + +unsigned char* QgsMssqlGeometryParser::ParseSqlGeometry(unsigned char* pszInput, int nLen) +{ + if (nLen < 10) + { + QgsDebugMsg( "ParseSqlGeometry not enough data" ); + DumpMemoryToLog("Not enough data", pszInput, nLen); + return NULL; + } + + pszData = pszInput; + nWkbMaxLen = nLen; + + /* store the SRS id for further use */ + nSRSId = ReadInt32(0); + + if ( ReadByte(4) != 1 ) + { + QgsDebugMsg( "ParseSqlGeometry corrupt data" ); + DumpMemoryToLog("Corrupt data", pszInput, nLen); + return NULL; + } + + chProps = ReadByte(5); + + if ( chProps & SP_HASMVALUES ) + nPointSize = 32; + else if ( chProps & SP_HASZVALUES ) + nPointSize = 24; + else + nPointSize = 16; + + /* store byte order */ + chByteOrder = QgsApplication::endian(); + + pszWkb = new unsigned char[nLen]; // wkb should be less or equal in size + nWkbLen = 0; + + if ( chProps & SP_ISSINGLEPOINT ) + { + // single point geometry + nPointPos = 6; + + if (nLen < 6 + nPointSize) + { + free(pszWkb); + QgsDebugMsg( "ParseSqlGeometry not enough data" ); + DumpMemoryToLog("Not enough data", pszInput, nLen); + return NULL; + } + + CopyPoint(0); + } + else if ( chProps & SP_ISSINGLELINESEGMENT ) + { + // single line segment with 2 points + nPointPos = 6; + + if (nLen < 6 + 2 * nPointSize) + { + free(pszWkb); + QgsDebugMsg( "ParseSqlGeometry not enough data" ); + DumpMemoryToLog("Not enough data", pszInput, nLen); + return NULL; + } + + // copy byte order + CopyBytes( &chByteOrder, 1 ); + // copy type + int wkbType; + if ( chProps & SP_HASZVALUES ) + wkbType = QGis::WKBLineString25D; + else + wkbType = QGis::WKBLineString; + CopyBytes( &wkbType, 4 ); + // copy point count + int iCount = 2; + CopyBytes( &iCount, 4 ); + // copy points + CopyBytes(pszData + nPointPos, nPointSize * 2); + } + else + { + // complex geometries + nNumPoints = ReadInt32(6); + + if ( nNumPoints <= 0 ) + { + free(pszWkb); + return NULL; + } + + // position of the point array + nPointPos = 10; + + // position of the figures + nFigurePos = nPointPos + nPointSize * nNumPoints + 4; + + if (nLen < nFigurePos) + { + free(pszWkb); + QgsDebugMsg( "ParseSqlGeometry not enough data" ); + DumpMemoryToLog("Not enough data", pszInput, nLen); + return NULL; + } + + nNumFigures = ReadInt32(nFigurePos - 4); + + if ( nNumFigures <= 0 ) + { + free(pszWkb); + return NULL; + } + + // position of the shapes + nShapePos = nFigurePos + 5 * nNumFigures + 4; + + if (nLen < nShapePos) + { + free(pszWkb); + QgsDebugMsg( "ParseSqlGeometry not enough data" ); + DumpMemoryToLog("Not enough data", pszInput, nLen); + return NULL; + } + + nNumShapes = ReadInt32(nShapePos - 4); + + if (nLen < nShapePos + 9 * nNumShapes) + { + free(pszWkb); + QgsDebugMsg( "ParseSqlGeometry not enough data" ); + DumpMemoryToLog("Not enough data", pszInput, nLen); + return NULL; + } + + if ( nNumShapes <= 0 ) + { + free(pszWkb); + return NULL; + } + + // pick up the root shape + if ( ParentOffset(0) != 0xFFFFFFFF) + { + free(pszWkb); + QgsDebugMsg( "ParseSqlGeometry corrupt data" ); + DumpMemoryToLog("Not enough data", pszInput, nLen); + return NULL; + } + + // determine the shape type + switch (ShapeType(0)) + { + case ST_POINT: + ReadPoint(0); + break; + case ST_LINESTRING: + ReadLineString(0); + break; + case ST_POLYGON: + ReadPolygon(0); + break; + case ST_MULTIPOINT: + ReadMultiPoint(0); + break; + case ST_MULTILINESTRING: + ReadMultiLineString(0); + break; + case ST_MULTIPOLYGON: + ReadMultiPolygon(0); + break; + //case ST_GEOMETRYCOLLECTION: + //ReadGeometryCollection(0); + //break; + default: + free(pszWkb); + QgsDebugMsg( "ParseSqlGeometry unsupported geometry type" ); + DumpMemoryToLog("Unsupported geometry type", pszInput, nLen); + return NULL; + } + } + + return pszWkb; +} + diff --git a/src/providers/mssql/qgsmssqlnewconnection.cpp b/src/providers/mssql/qgsmssqlnewconnection.cpp new file mode 100644 index 000000000000..b8119c45e5f1 --- /dev/null +++ b/src/providers/mssql/qgsmssqlnewconnection.cpp @@ -0,0 +1,176 @@ +/*************************************************************************** + qgsmssqlnewconnection.cpp - description + ------------------- + begin : 2011-10-08 + copyright : (C) 2011 by Tamas Szekeres + email : szekerest at gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 +#include + +#include "qgsmssqlnewconnection.h" +#include "qgsmssqlprovider.h" +#include "qgscontexthelp.h" + +QgsMssqlNewConnection::QgsMssqlNewConnection( 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 = "/MSSQL/connections/" + connName; + txtService->setText( settings.value( key + "/service" ).toString() ); + txtHost->setText( settings.value( key + "/host" ).toString() ); + txtDatabase->setText( settings.value( key + "/database" ).toString() ); + cb_geometryColumns->setChecked( settings.value( key + "/geometryColumns", true ).toBool() ); + cb_allowGeometrylessTables->setChecked( settings.value( key + "/allowGeometrylessTables", true ).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 QgsMssqlNewConnection::accept() +{ + QSettings settings; + QString baseKey = "/MSSQL/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 + "/service", txtService->text() ); + settings.setValue( baseKey + "/host", txtHost->text() ); + settings.setValue( baseKey + "/database", txtDatabase->text() ); + settings.setValue( baseKey + "/username", chkStoreUsername->isChecked() ? txtUsername->text() : "" ); + settings.setValue( baseKey + "/password", chkStorePassword->isChecked() ? txtPassword->text() : "" ); + settings.setValue( baseKey + "/saveUsername", chkStoreUsername->isChecked() ? "true" : "false" ); + settings.setValue( baseKey + "/savePassword", chkStorePassword->isChecked() ? "true" : "false" ); + settings.setValue( baseKey + "/geometryColumns", cb_geometryColumns->isChecked() ); + settings.setValue( baseKey + "/allowGeometrylessTables", cb_allowGeometrylessTables->isChecked() ); + settings.setValue( baseKey + "/estimatedMetadata", cb_useEstimatedMetadata->isChecked() ); + + QDialog::accept(); +} + +void QgsMssqlNewConnection::on_btnConnect_clicked() +{ + testConnection(); +} + +/** end Autoconnected SLOTS **/ + +QgsMssqlNewConnection::~QgsMssqlNewConnection() +{ +} + +void QgsMssqlNewConnection::testConnection() +{ + if (txtService->text().isEmpty()) + { + if (txtHost->text().isEmpty()) + { + QMessageBox::information( this, + tr( "Test connection" ), + tr( "Connection failed - Host name hasn't been specified.\n\n" ) ); + return; + } + + if (txtDatabase->text().isEmpty()) + { + QMessageBox::information( this, + tr( "Test connection" ), + tr( "Connection failed - Database name hasn't been specified.\n\n" ) ); + return; + } + } + + QSqlDatabase db = QgsMssqlProvider::GetDatabase( txtService->text().trimmed(), + txtHost->text().trimmed(), txtDatabase->text().trimmed(), + txtUsername->text().trimmed(), txtPassword->text().trimmed() ); + + if ( db.isOpen() ) + db.close(); + + if ( !db.open() ) + { + QMessageBox::information( this, + tr( "Test connection" ), + db.lastError( ).text( ) ); + } + else + { + QMessageBox::information( this, + tr( "Test connection" ), + tr( "Connection to %1 was successful" ).arg( txtDatabase->text() ) ); + } +} diff --git a/src/providers/mssql/qgsmssqlnewconnection.h b/src/providers/mssql/qgsmssqlnewconnection.h new file mode 100644 index 000000000000..34ce74a9ab2e --- /dev/null +++ b/src/providers/mssql/qgsmssqlnewconnection.h @@ -0,0 +1,45 @@ +/*************************************************************************** + qgsmssqlnewconnection.h - description + ------------------- + begin : 2011-10-08 + copyright : (C) 2011 by Tamas Szekeres + email : szekerest at gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 QGSMSSQLNEWCONNECTION_H +#define QGSMSSQLNEWCONNECTION_H +#include "ui_qgsmssqlnewconnectionbase.h" +#include "qgisgui.h" +#include "qgscontexthelp.h" + +/*! \class QgsMssqlNewConnection + * \brief Dialog to allow the user to configure and save connection + * information for an MSSQL database + */ +class QgsMssqlNewConnection : public QDialog, private Ui::QgsMssqlNewConnectionBase +{ + Q_OBJECT + public: + //! Constructor + QgsMssqlNewConnection( QWidget *parent = 0, const QString& connName = QString::null, Qt::WFlags fl = QgisGui::ModalDialogFlags ); + //! Destructor + ~QgsMssqlNewConnection(); + //! Tests the connection using the parameters supplied + void testConnection(); + 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 // QGSMSSQLNEWCONNECTION_H diff --git a/src/providers/mssql/qgsmssqlprovider.cpp b/src/providers/mssql/qgsmssqlprovider.cpp new file mode 100644 index 000000000000..ea1eb84bad1b --- /dev/null +++ b/src/providers/mssql/qgsmssqlprovider.cpp @@ -0,0 +1,1541 @@ +/*************************************************************************** + qgsmssqlprovider.cpp - Data provider for mssql server + ------------------- + begin : 2011-10-08 + copyright : (C) 2011 by Tamas Szekeres + email : szekerest at gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "qgsmssqlprovider.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "qgsapplication.h" +#include "qgsdataprovider.h" +#include "qgsfeature.h" +#include "qgsfield.h" +#include "qgsgeometry.h" +#include "qgslogger.h" +#include "qgsmessageoutput.h" +#include "qgsrectangle.h" +#include "qgis.h" + +#include "qgsmssqlsourceselect.h" +#include "qgsmssqldataitems.h" + +// grab sql constants +#ifdef WIN32 +# include +#endif +#include + +static const QString TEXT_PROVIDER_KEY = "mssql"; +static const QString TEXT_PROVIDER_DESCRIPTION = "MS SQL spatial data provider"; + +QgsMssqlProvider::QgsMssqlProvider( QString uri ) + : QgsVectorDataProvider( uri ) + , mFieldCount( 0 ) + , mCrs() + , mWkbType( QGis::WKBUnknown ) +{ + QgsDataSourceURI anUri = QgsDataSourceURI( uri ); + + mValid = true; + + mUseWkb = false; + mSkipFailures = true; + + mDatabase = GetDatabase( anUri.service(), anUri.host(), anUri.database(), anUri.username(), anUri.password() ); + + if ( !OpenDatabase( mDatabase ) ) + { + setLastError( mDatabase.lastError( ).text( ) ); + mValid = false; + return; + } + + // Database successfully opened; we can now issue SQL commands. + if ( !anUri.schema().isEmpty() ) + mSchemaName = anUri.schema(); + else + mSchemaName = "dbo"; + + if ( !anUri.table().isEmpty() ) + { + // the layer name has been specified + mTableName = anUri.table(); + QStringList sl = mTableName.split( '.' ); + if ( sl.length() == 2) + { + mSchemaName = sl[0]; + mTableName = sl[1]; + } + mTables = QStringList( mTableName ); + } + else + { + // Get a list of table + mTables = mDatabase.tables( QSql::Tables); + if ( mTables.count() > 0 ) + mTableName = mTables[0]; + else + mValid = false; + } + if (mValid) + { + if (!anUri.keyColumn().isEmpty()) + mFidColName = anUri.keyColumn(); + + if (!anUri.geometryColumn().isEmpty()) + mGeometryColName = anUri.geometryColumn(); + + loadMetadata(); + loadFields(); + UpdateStatistics(); + + if (mGeometryColName.isEmpty()) + { + // table contains no geometries + mWkbType = QGis::WKBNoGeometry; + mSRId = 0; + } + + if (mFidColName.isEmpty()) + mValid = false; + } + + //fill type names into sets + mNativeTypes + // integer types + << QgsVectorDataProvider::NativeType( tr( "8 Bytes integer" ), "bigint", QVariant::Int ) + << QgsVectorDataProvider::NativeType( tr( "4 Bytes integer" ), "int", QVariant::Int ) + << QgsVectorDataProvider::NativeType( tr( "2 Bytes integer" ), "smallint", QVariant::Int ) + << QgsVectorDataProvider::NativeType( tr( "1 Bytes integer" ), "tinyint", QVariant::Int ) + << QgsVectorDataProvider::NativeType( tr( "Decimal number (numeric)" ), "numeric", QVariant::Double, 1, 20, 0, 20 ) + << QgsVectorDataProvider::NativeType( tr( "Decimal number (decimal)" ), "decimal", QVariant::Double, 1, 20, 0, 20 ) + + // floating point + << QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), "real", QVariant::Double ) + << QgsVectorDataProvider::NativeType( tr( "Decimal number (double)" ), "float", QVariant::Double ) + + // string types + << QgsVectorDataProvider::NativeType( tr( "Text, fixed length (char)" ), "char", QVariant::String, 1, 255 ) + << QgsVectorDataProvider::NativeType( tr( "Text, limited variable length (varchar)" ), "varchar", QVariant::String, 1, 255 ) + << QgsVectorDataProvider::NativeType( tr( "Text, fixed length unicode (nchar)" ), "nchar", QVariant::String, 1, 255 ) + << QgsVectorDataProvider::NativeType( tr( "Text, limited variable length unicode (nvarchar)" ), "nvarchar", QVariant::String, 1, 255 ) + << QgsVectorDataProvider::NativeType( tr( "Text, unlimited length (text)" ), "text", QVariant::String ) + << QgsVectorDataProvider::NativeType( tr( "Text, unlimited length unicode (ntext)" ), "text", QVariant::String ) + ; +} + +QgsMssqlProvider::~QgsMssqlProvider() +{ +} + +bool QgsMssqlProvider::OpenDatabase(QSqlDatabase db) +{ + if ( !db.isOpen() ) + { + if ( !db.open() ) + { + return false; + } + } + return true; +} + +QSqlDatabase QgsMssqlProvider::GetDatabase(QString driver, QString host, QString database, QString username, QString password) +{ + QSqlDatabase db; + QString connectionName; + if (driver.isEmpty()) + { + if (host.isEmpty()) + { + QgsDebugMsg( "QgsMssqlProvider host name not specified" ); + return db; + } + + if (database.isEmpty()) + { + QgsDebugMsg( "QgsMssqlProvider database name not specified" ); + return db; + } + connectionName = host + "." + database; + } + else + connectionName = driver; + + if ( !QSqlDatabase::contains(connectionName) ) + db = QSqlDatabase::addDatabase( "QODBC", connectionName ); + else + db = QSqlDatabase::database(connectionName); + + db.setHostName( host ); + QString connectionString = ""; + if ( !driver.isEmpty() ) + { + // driver was specified explicitly + connectionString = driver; + } + else + { +#ifdef WIN32 + connectionString = "driver={SQL Server}"; +#else + connectionString = "driver={FreeDTS}"; +#endif + if ( !host.isEmpty() ) + connectionString += ";server=" + host; + + if ( !database.isEmpty() ) + connectionString += ";database=" + database; + + if ( password.isEmpty() ) + connectionString += ";trusted_connection=yes"; + else + connectionString += ";uid=" + username + ";pwd=" + password; + } + + if (!username.isEmpty()) + db.setUserName( username ); + + if (!password.isEmpty()) + db.setPassword( password ); + + db.setDatabaseName( connectionString ); + return db; +} + +QVariant::Type QgsMssqlProvider::DecodeODBCType(int sqltype) +{ + QVariant::Type type = QVariant::Invalid; + switch (sqltype) { + case SQL_DECIMAL: + case SQL_NUMERIC: + case SQL_REAL: + case SQL_FLOAT: + case SQL_DOUBLE: + type = QVariant::Double; + break; + case SQL_SMALLINT: + case SQL_INTEGER: + case SQL_BIT: + case SQL_TINYINT: + type = QVariant::Int; + break; + case SQL_BIGINT: + type = QVariant::LongLong; + break; + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + type = QVariant::ByteArray; + break; + case SQL_DATE: + case SQL_TYPE_DATE: + type = QVariant::Date; + break; + case SQL_TIME: + case SQL_TYPE_TIME: + type = QVariant::Time; + break; + case SQL_TIMESTAMP: + case SQL_TYPE_TIMESTAMP: + type = QVariant::DateTime; + break; +#ifndef Q_ODBC_VERSION_2 + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: + type = QVariant::String; + break; +#endif + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + type = QVariant::String; + break; + default: + type = QVariant::ByteArray; + break; + } + return type; +} + +void QgsMssqlProvider::loadMetadata() +{ + mSRId = 0; + mWkbType = QGis::WKBUnknown; + + mQuery = QSqlQuery( mDatabase ); + mQuery.setForwardOnly(true); + mQuery.exec( QString("select f_geometry_column, coord_dimension, srid, geometry_type from geometry_columns where f_table_schema = '%1' and f_table_name = '%2'").arg( mSchemaName ).arg( mTableName ) ); + if (mQuery.isActive()) + { + if ( mQuery.next() ) + { + mGeometryColName = mQuery.value(0).toString(); + mSRId = mQuery.value(2).toInt(); + mWkbType = getWkbType( mQuery.value(3).toString(), mQuery.value(1).toInt() ); + } + } +} + +void QgsMssqlProvider::loadFields() +{ + mAttributeFields.clear(); + // get field spec + mQuery = QSqlQuery( mDatabase ); + mQuery.setForwardOnly(true); + mQuery.exec( QString("exec sp_columns N'%1', NULL, NULL, NULL, NULL").arg( mTableName ) ); + if (mQuery.isActive()) + { + int i = 0; + while ( mQuery.next() ) + { + QString sqlTypeName = mQuery.value(5).toString(); + QVariant::Type sqlType = DecodeODBCType( mQuery.value(4).toInt() ); + if (sqlTypeName == "geometry" || sqlTypeName == "geography") + { + mGeometryColName = mQuery.value(3).toString(); + mGeometryColType = sqlTypeName; + } + else + { + if (sqlTypeName == "int identity" || sqlTypeName == "bigint identity") + mFidColName = mQuery.value(3).toString(); + mAttributeFields.insert( + i, QgsField( + mQuery.value(3).toString(), sqlType, + sqlTypeName, + mQuery.value(7).toInt(), + mQuery.value(6).toInt() ) ); + ++i; + } + } + } +} + + +QString QgsMssqlProvider::storageType() const +{ + return "MS SQL spatial database"; +} + +bool QgsMssqlProvider::featureAtId( QgsFeatureId featureId, + QgsFeature& feature, + bool fetchGeometry, + QgsAttributeList fetchAttributes ) +{ + // build sql statement + QString query("select "); + mFieldCount = 0; + for ( QgsAttributeList::iterator it = fetchAttributes.begin(); it != fetchAttributes.end(); ++it ) + { + if (mFieldCount != 0) + query += ","; + query += "[" + mAttributeFields[*it].name() + "]"; + ++mFieldCount; + } + + mFidCol = -1; + if ( !mFidColName.isEmpty() ) + { + if (mFieldCount != 0) + query += ","; + query += "[" + mFidColName + "]"; + mFidCol = mFieldCount; + ++mFieldCount; + } + else + return false; + mGeometryCol = -1; + if (fetchGeometry && !mGeometryColName.isEmpty() ) + { + if (mFieldCount != 0) + query += ","; + query += "[" + mGeometryColName + "]"; + mGeometryCol = mFieldCount; + ++mFieldCount; + } + query += " from "; + if ( !mSchemaName.isEmpty() ) + query += "[" + mSchemaName + "]."; + + query += "[" + mTableName + "]"; + // set attribute filter + query += QString( " where [%1] = %2" ).arg( mFidColName, QString::number(featureId)); + + mFetchGeom = fetchGeometry; + mAttributesToFetch = fetchAttributes; + // issue the sql query + mQuery = QSqlQuery(mDatabase); + mQuery.setForwardOnly(true); + mQuery.exec( query ); + + return nextFeature(feature); +} + + +bool QgsMssqlProvider::nextFeature( QgsFeature& feature ) +{ + feature.setValid( false ); + if ( !mValid ) + { + QgsDebugMsg( "Read attempt on an invalid mssql data source" ); + return false; + } + + if ( !mQuery.isActive() ) + { + QgsDebugMsg( "Read attempt on inactive query" ); + return false; + } + + feature.clearAttributeMap(); + + if ( mQuery.next() ) + { + int col = 0; + for ( QgsAttributeList::iterator it = mAttributesToFetch.begin(); it != mAttributesToFetch.end(); ++it ) + { + feature.addAttribute( *it, mQuery.value(col) ); + col++; + } + + if (mFidCol >=0) + { + feature.setFeatureId( mQuery.value(col).toInt() ); + col++; + } + + if (mGeometryCol >=0) + { + QByteArray ar = mQuery.value(col).toByteArray(); + unsigned char* wkb = parser.ParseSqlGeometry( (unsigned char*)ar.data(), ar.size() ); + if (wkb) + { + feature.setGeometryAndOwnership( wkb, parser.GetWkbLen() ); + } + col++; + } + + feature.setValid( true ); + return true; + } + return false; +} // nextFeature + + +void QgsMssqlProvider::select( QgsAttributeList fetchAttributes, + QgsRectangle rect, + bool fetchGeometry, + bool useIntersect ) +{ + + // build sql statement + mStatement = QString("select "); + mFieldCount = 0; + for ( QgsAttributeList::iterator it = fetchAttributes.begin(); it != fetchAttributes.end(); ++it ) + { + if (mFieldCount != 0) + mStatement += ","; + mStatement += "[" + mAttributeFields[*it].name() + "]"; + ++mFieldCount; + } + + mFidCol = -1; + if ( !mFidColName.isEmpty() ) + { + if (mFieldCount != 0) + mStatement += ","; + mStatement += "[" + mFidColName + "]"; + mFidCol = mFieldCount; + ++mFieldCount; + } + mGeometryCol = -1; + if (fetchGeometry && !mGeometryColName.isEmpty() ) + { + if (mFieldCount != 0) + mStatement += ","; + mStatement += "[" + mGeometryColName + "]"; + mGeometryCol = mFieldCount; + ++mFieldCount; + } + + mStatement += " from "; + if ( !mSchemaName.isEmpty() ) + mStatement += "[" + mSchemaName + "]."; + + mStatement += "[" + mTableName + "]"; + // set spatial filter + if ( !rect.isEmpty() ) + { + mStatement += QString( " where [%1].STIntersects([%2]::STGeomFromText('POLYGON((%3))',%4)) = 1" ).arg( + mGeometryColName, mGeometryColType, rect.asPolygon(), QString::number(mSRId)); + } + mFetchGeom = fetchGeometry; + mAttributesToFetch = fetchAttributes; + // issue the sql query + mQuery = QSqlQuery(mDatabase); + mQuery.setForwardOnly(true); + if (mFieldCount > 0) + { + mQuery.exec( mStatement ); + if ( !mQuery.exec( mStatement ) ) + { + QString msg = mQuery.lastError().text(); + QgsDebugMsg( msg ); + } + } + else + QgsDebugMsg( "QgsMssqlProvider::select no fields have been requested" ); +} + +// update the extent, feature count, wkb type and srid for this layer +void QgsMssqlProvider::UpdateStatistics() +{ + mNumberFeatures = 0; + // get features to calculate the statistics + mQuery = QSqlQuery( mDatabase ); + mQuery.setForwardOnly(true); + if ( mSchemaName.isEmpty() ) + mQuery.exec( QString("select [%1] from [%2]").arg( mGeometryColName, mTableName ) ); + else + mQuery.exec( QString("select [%1] from [%2].[%3]").arg( mGeometryColName, mSchemaName, mTableName ) ); + + if (mQuery.isActive()) + { + QgsGeometry geom; + while ( mQuery.next() ) + { + QByteArray ar = mQuery.value(0).toByteArray(); + unsigned char* wkb = parser.ParseSqlGeometry( (unsigned char*)ar.data(), ar.size() ); + if (wkb) + { + geom.fromWkb( wkb, parser.GetWkbLen() ); + QgsRectangle rect = geom.boundingBox(); + + if ( mNumberFeatures > 0 ) + { + if (rect.xMinimum() < mExtent.xMinimum()) + mExtent.setXMinimum(rect.xMinimum()); + if (rect.yMinimum() < mExtent.yMinimum()) + mExtent.setYMinimum(rect.yMinimum()); + if (rect.xMaximum() > mExtent.xMaximum()) + mExtent.setXMaximum(rect.xMaximum()); + if (rect.yMaximum() > mExtent.yMaximum()) + mExtent.setYMaximum(rect.yMaximum()); + } + else + { + mExtent = rect; + mWkbType = geom.wkbType(); + mSRId = parser.GetSRSId(); + } + ++mNumberFeatures; + } + } + } +} + +// Return the extent of the layer +QgsRectangle QgsMssqlProvider::extent() +{ + if ( mExtent.isEmpty() ) + UpdateStatistics(); + return mExtent; +} + +/** + * Return the feature type + */ +QGis::WkbType QgsMssqlProvider::geometryType() const +{ + return mWkbType; +} + +/** + * Return the feature type + */ +long QgsMssqlProvider::featureCount() const +{ + return mNumberFeatures; +} + +/** + * Return the number of fields + */ +uint QgsMssqlProvider::fieldCount() const +{ + return mAttributeFields.size(); +} + + +const QgsFieldMap & QgsMssqlProvider::fields() const +{ + return mAttributeFields; +} + +void QgsMssqlProvider::rewind() +{ + if ( !mStatement.isEmpty() ) + { + // reissue the sql query + mQuery = QSqlQuery(mDatabase); + mQuery.setForwardOnly(true); + mQuery.exec( mStatement ); + } +} + +bool QgsMssqlProvider::isValid() +{ + return mValid; +} + +bool QgsMssqlProvider::addFeatures( QgsFeatureList & flist ) +{ + for ( QgsFeatureList::iterator it = flist.begin(); it != flist.end(); ++it ) + { + QString statement; + QString values; + if ( mSchemaName.isEmpty() ) + statement = QString( "INSERT INTO [%1].[%2] (" ).arg( QString( "dbo" ), mTableName ); + else + statement = QString( "INSERT INTO [%1].[%2] (" ).arg( mSchemaName, mTableName ); + + bool first = true; + mQuery = QSqlQuery( mDatabase ); + mQuery.setForwardOnly(true); + + const QgsAttributeMap& attrs = it->attributeMap(); + + for ( QgsAttributeMap::const_iterator it2 = attrs.begin(); it2 != attrs.end(); ++it2 ) + { + QgsField fld = mAttributeFields[it2.key()]; + + if ( fld.typeName().endsWith(" identity", Qt::CaseInsensitive) ) + continue; // skip identity field + + if ( fld.name().isEmpty() ) + continue; // invalid + + if ( !first ) + { + statement += ","; + values += ","; + } + else + first = false; + + statement += QString( "[%1]" ).arg( fld.name() ); + values += QString( "?" ); + } + + // append geometry column name + if ( !mGeometryColName.isEmpty() ) + { + if ( !first ) + { + statement += ","; + values += ","; + } + + statement += QString( "[%1]" ).arg( mGeometryColName ); + if (mGeometryColType == "geometry") + { + if (mUseWkb) + values += QString("geometry::STGeomFromWKB(%1,%2).MakeValid()").arg( + QString("?") , QString::number(mSRId) ); + else + values += QString("geometry::STGeomFromText(%1,%2).MakeValid()").arg( + QString("?") , QString::number(mSRId) ); + } + else + { + if (mUseWkb) + values += QString("geography::STGeomFromWKB(%1,%2)").arg( + QString("?"), QString::number(mSRId) ); + else + values += QString("geography::STGeomFromText(%1,%2)").arg( + QString("?"), QString::number(mSRId) ); + } + } + + statement += ") VALUES (" + values + ")"; + + if (!mQuery.prepare( statement )) + { + QString msg = mQuery.lastError().text(); + QgsDebugMsg( msg ); + if (!mSkipFailures) + return false; + else + continue; + } + + for ( QgsAttributeMap::const_iterator it2 = attrs.begin(); it2 != attrs.end(); ++it2 ) + { + QgsField fld = mAttributeFields[it2.key()]; + + if ( fld.typeName().endsWith(" identity", Qt::CaseInsensitive) ) + continue; // skip identity field + + if ( fld.name().isEmpty() ) + continue; // invalid + + // use prepared statement to prevent from sql injection + mQuery.addBindValue( *it2 ); + } + + if ( !mGeometryColName.isEmpty() ) + { + QgsGeometry *geom = it->geometry(); + if (mUseWkb) + { + QByteArray bytea = QByteArray((char*)geom->asWkb(), geom->wkbSize()); + mQuery.addBindValue(bytea, QSql::In | QSql::Binary); + } + else + { + QString wkt = geom->exportToWkt(); + mQuery.addBindValue(wkt); + } + } + + if ( !mQuery.exec() ) + { + QString msg = mQuery.lastError().text(); + QgsDebugMsg( msg ); + if (!mSkipFailures) + return false; + } + } + + return true; +} + +bool QgsMssqlProvider::addAttributes( const QList &attributes ) +{ + QString statement; + + for ( QList::const_iterator it = attributes.begin(); it != attributes.end(); ++it ) + { + QString type = it->typeName(); + if ( type == "char" || type == "varchar" ) + { + if ( it->length() > 0 ) + type = QString( "%1(%2)" ).arg( type ).arg( it->length() ); + } + else if ( type == "numeric" || type == "decimal" ) + { + if ( it->length() > 0 && it->precision() > 0 ) + type = QString( "%1(%2,%3)" ).arg( type ).arg( it->length() ).arg( it->precision() ); + } + + if ( statement.isEmpty() ) + { + if ( mSchemaName.isEmpty() ) + statement = QString( "ALTER TABLE [%1].[%2] ADD " ).arg( + QString( "dbo" ), mTableName); + else + statement = QString( "ALTER TABLE [%1].[%2] ADD " ).arg( + mSchemaName, mTableName); + } + else + statement += ","; + + statement += QString("[%1] %2").arg( it->name(), type ); + } + + mQuery = QSqlQuery( mDatabase ); + mQuery.setForwardOnly(true); + if ( !mQuery.exec( statement ) ) + { + QString msg = mQuery.lastError().text(); + QgsDebugMsg( msg ); + return false; + } + + return true; +} + +bool QgsMssqlProvider::deleteAttributes( const QgsAttributeIds &attributes ) +{ + QString statement; + + for ( QgsAttributeIds::const_iterator it = attributes.begin(); it != attributes.end(); ++it ) + { + QgsFieldMap::const_iterator field_it = mAttributeFields.find( *it ); + if ( field_it == mAttributeFields.constEnd() ) + continue; + + if ( statement.isEmpty() ) + { + if ( mSchemaName.isEmpty() ) + statement = QString( "ALTER TABLE [%1].[%2] DROP COLUMN " ).arg( QString( "dbo" ), mTableName ); + else + statement = QString( "ALTER TABLE [%1].[%2] DROP COLUMN " ).arg( mSchemaName, mTableName ); + } + else + statement += ","; + + statement += QString( "[%1]" ).arg( field_it->name() ); + + //delete the attribute from attributeFields + mAttributeFields.remove( *it ); + } + + mQuery = QSqlQuery( mDatabase ); + mQuery.setForwardOnly(true); + return mQuery.exec( statement ); +} + + +bool QgsMssqlProvider::changeAttributeValues( const QgsChangedAttributesMap & attr_map ) +{ + if ( attr_map.isEmpty() ) + return true; + + for ( QgsChangedAttributesMap::const_iterator it = attr_map.begin(); it != attr_map.end(); ++it ) + { + QgsFeatureId fid = it.key(); + + // skip added features + if ( FID_IS_NEW( fid ) ) + continue; + + QString statement; + if ( mSchemaName.isEmpty() ) + statement = QString( "UPDATE [%1].[%2] SET " ).arg( QString( "dbo" ), mTableName ); + else + statement = QString( "UPDATE [%1].[%2] SET " ).arg( mSchemaName, mTableName ); + + bool first = true; + mQuery = QSqlQuery( mDatabase ); + mQuery.setForwardOnly(true); + + const QgsAttributeMap& attrs = it.value(); + + for ( QgsAttributeMap::const_iterator it2 = attrs.begin(); it2 != attrs.end(); ++it2 ) + { + QgsField fld = mAttributeFields[it2.key()]; + + if ( fld.typeName().endsWith(" identity", Qt::CaseInsensitive) ) + continue; // skip identity field + + if ( fld.name().isEmpty() ) + continue; // invalid + + if ( !first ) + statement += ","; + else + first = false; + + statement += QString( "[%1]=?" ).arg( fld.name() ); + } + + if (first) + return true; // no fields have been changed + + // set attribute filter + statement += QString( " WHERE [%1]=%2" ).arg( mFidColName, FID_TO_STRING( fid )); + + if (!mQuery.prepare( statement )) + { + QString msg = mQuery.lastError().text(); + QgsDebugMsg( msg ); + return false; + } + + for ( QgsAttributeMap::const_iterator it2 = attrs.begin(); it2 != attrs.end(); ++it2 ) + { + QgsField fld = mAttributeFields[it2.key()]; + + if ( fld.typeName().endsWith(" identity", Qt::CaseInsensitive) ) + continue; // skip identity field + + if ( fld.name().isEmpty() ) + continue; // invalid + + // use prepared statement to prevent from sql injection + mQuery.addBindValue( *it2 ); + } + + if ( !mQuery.exec() ) + { + QString msg = mQuery.lastError().text(); + QgsDebugMsg( msg ); + return false; + } + } + + return true; +} + +bool QgsMssqlProvider::changeGeometryValues( QgsGeometryMap & geometry_map ) +{ + if ( geometry_map.isEmpty() ) + return true; + + for ( QgsGeometryMap::iterator it = geometry_map.begin(); it != geometry_map.end(); ++it ) + { + QgsFeatureId fid = it.key(); + // skip added features + if ( FID_IS_NEW( fid ) ) + continue; + + QString statement; + if ( mSchemaName.isEmpty() ) + statement = QString( "UPDATE [%1].[%2] SET " ).arg( QString( "dbo" ), mTableName ); + else + statement = QString( "UPDATE [%1].[%2] SET " ).arg( mSchemaName, mTableName ); + + mQuery = QSqlQuery( mDatabase ); + mQuery.setForwardOnly(true); + + if (mGeometryColType == "geometry") + { + if (mUseWkb) + statement += QString("[%1]=geometry::STGeomFromWKB(%2,%3).MakeValid()").arg( + mGeometryColName, QString("?") , QString::number(mSRId) ); + else + statement += QString("[%1]=geometry::STGeomFromText(%2,%3).MakeValid()").arg( + mGeometryColName, QString("?") , QString::number(mSRId) ); + } + else + { + if (mUseWkb) + statement += QString("[%1]=geography::STGeomFromWKB(%2,%3)").arg( + mGeometryColName, QString("?") , QString::number(mSRId) ); + else + statement += QString("[%1]=geography::STGeomFromText(%2,%3)").arg( + mGeometryColName, QString("?") , QString::number(mSRId) ); + } + + // set attribute filter + statement += QString( " WHERE [%1]=%2" ).arg( mFidColName, FID_TO_STRING( fid )); + + if (!mQuery.prepare( statement )) + { + QString msg = mQuery.lastError().text(); + QgsDebugMsg( msg ); + return false; + } + + // add geometry param + if (mUseWkb) + { + QByteArray bytea = QByteArray((char*)it->asWkb(), it->wkbSize()); + mQuery.addBindValue(bytea, QSql::In | QSql::Binary); + } + else + { + QString wkt = it->exportToWkt(); + mQuery.addBindValue(wkt); + } + + if ( !mQuery.exec() ) + { + QString msg = mQuery.lastError().text(); + QgsDebugMsg( msg ); + return false; + } + } + + return true; +} + +bool QgsMssqlProvider::deleteFeatures( const QgsFeatureIds & id ) +{ + QString featureIds; + for ( QgsFeatureIds::const_iterator it = id.begin(); it != id.end(); ++it ) + { + if (featureIds.isEmpty()) + featureIds = FID_TO_STRING( *it ); + else + featureIds += "," + FID_TO_STRING( *it ); + } + + mQuery = QSqlQuery( mDatabase ); + mQuery.setForwardOnly(true); + QString statement; + if ( mSchemaName.isEmpty() ) + statement = QString("DELETE FROM [%1].[%2] WHERE [%3] IN (%4)").arg( QString("dbo"), + mTableName, mFidColName, featureIds ); + else + statement = QString("DELETE FROM [%1].[%2] WHERE [%3] IN (%4)").arg( mSchemaName, + mTableName, mFidColName, featureIds ); + + return mQuery.exec( statement ); +} + +int QgsMssqlProvider::capabilities() const +{ + return CreateSpatialIndex | CreateAttributeIndex | AddFeatures | DeleteFeatures | + ChangeAttributeValues | ChangeGeometries | AddAttributes | DeleteAttributes | + QgsVectorDataProvider::SelectAtId | QgsVectorDataProvider::SelectGeometryAtId; +} + +bool QgsMssqlProvider::createSpatialIndex() +{ + mQuery = QSqlQuery(mDatabase); + mQuery.setForwardOnly(true); + QString statement; + if ( mSchemaName.isEmpty() ) + statement = QString("CREATE SPATIAL INDEX [qgs_%1_sidx] ON [%2].[%3] ( [%4] )").arg( + mGeometryColName, QString("dbo"), mTableName, mGeometryColName ); + else + statement = QString("CREATE SPATIAL INDEX [qgs_%1_sidx] ON [%2].[%3] ( [%4] )").arg( + mGeometryColName, mSchemaName, mTableName, mGeometryColName ); + + if (mGeometryColType == "geometry") + { + statement += QString(" USING GEOMETRY_GRID WITH (BOUNDING_BOX =(%1, %2, %3, %4))").arg( + QString::number(mExtent.xMinimum()), QString::number(mExtent.yMinimum()), + QString::number(mExtent.xMaximum()), QString::number(mExtent.yMaximum()) ); + } + else + { + statement += " USING GEOGRAPHY_GRID"; + } + return mQuery.exec( statement ); +} + +bool QgsMssqlProvider::createAttributeIndex( int field ) +{ + mQuery = QSqlQuery(mDatabase); + mQuery.setForwardOnly(true); + QString statement; + + if ( field < 0 || field >= mAttributeFields.size() ) + { + QgsDebugMsg( "createAttributeIndex invalid index" ); + return false; + } + + if ( mSchemaName.isEmpty() ) + statement = QString("CREATE NONCLUSTERED INDEX [qgs_%1_idx] ON [%2].[%3] ( [%4] )").arg( + mGeometryColName, QString("dbo"), mTableName, mAttributeFields[field].name() ); + else + statement = QString("CREATE NONCLUSTERED INDEX [qgs_%1_idx] ON [%2].[%3] ( [%4] )").arg( + mGeometryColName, mSchemaName, mTableName, mAttributeFields[field].name() ); + + return mQuery.exec( statement ); +} + +QgsCoordinateReferenceSystem QgsMssqlProvider::crs() +{ + if ( !mCrs.isValid() && mSRId > 0 ) + { + // try to load crs + QSqlQuery query = QSqlQuery( mDatabase ); + query.setForwardOnly(true); + query.exec( QString("select srtext from spatial_ref_sys where srid = %1").arg( QString::number(mSRId) ) ); + if (query.isActive()) + { + if ( query.next() ) + { + if (mCrs.createFromWkt( query.value(0).toString() )) + return mCrs; + } + } + query.exec( QString("select well_known_text from sys.spatial_reference_systems where spatial_reference_id = %1").arg( QString::number(mSRId) ) ); + if (query.isActive()) + { + if ( query.next() ) + { + if (mCrs.createFromWkt( query.value(0).toString() )) + return mCrs; + } + } + } + return mCrs; +} + + +QString QgsMssqlProvider::name() const +{ + return TEXT_PROVIDER_KEY; +} // ::name() + + + +QString QgsMssqlProvider::description() const +{ + return TEXT_PROVIDER_DESCRIPTION; +} // QgsMssqlProvider::name() + +QStringList QgsMssqlProvider::subLayers() const +{ + return mTables; +} + +bool QgsMssqlProvider::convertField( QgsField &field ) +{ + QString fieldType = "nvarchar(max)"; //default to string + int fieldSize = field.length(); + int fieldPrec = field.precision(); + switch ( field.type() ) + { + case QVariant::LongLong: + fieldType = "bigint"; + fieldSize = -1; + fieldPrec = 0; + break; + + case QVariant::String: + fieldType = "nvarchar(max)"; + fieldPrec = -1; + break; + + case QVariant::Int: + fieldType = "int"; + fieldSize = -1; + fieldPrec = 0; + break; + + case QVariant::Double: + if ( fieldSize <= 0 || fieldPrec <= 0 ) + { + fieldType = "float"; + fieldSize = -1; + fieldPrec = -1; + } + else + { + fieldType = "decimal"; + } + break; + + default: + return false; + } + + field.setTypeName( fieldType ); + field.setLength( fieldSize ); + field.setPrecision( fieldPrec ); + return true; +} + +void QgsMssqlProvider::mssqlWkbTypeAndDimension( QGis::WkbType wkbType, QString &geometryType, int &dim ) +{ + switch ( wkbType ) + { + case QGis::WKBPoint25D: + dim = 3; + case QGis::WKBPoint: + geometryType = "POINT"; + break; + + case QGis::WKBLineString25D: + dim = 3; + case QGis::WKBLineString: + geometryType = "LINESTRING"; + break; + + case QGis::WKBPolygon25D: + dim = 3; + case QGis::WKBPolygon: + geometryType = "POLYGON"; + break; + + case QGis::WKBMultiPoint25D: + dim = 3; + case QGis::WKBMultiPoint: + geometryType = "MULTIPOINT"; + break; + + case QGis::WKBMultiLineString25D: + dim = 3; + case QGis::WKBMultiLineString: + geometryType = "MULTILINESTRING"; + break; + + case QGis::WKBMultiPolygon25D: + dim = 3; + case QGis::WKBMultiPolygon: + geometryType = "MULTIPOLYGON"; + break; + + case QGis::WKBUnknown: + geometryType = "GEOMETRY"; + break; + + case QGis::WKBNoGeometry: + default: + dim = 0; + break; + } +} + +QGis::WkbType QgsMssqlProvider::getWkbType( QString geometryType, int dim ) +{ + if ( dim == 3 ) + { + if (geometryType == "POINT") + return QGis::WKBPoint25D; + if (geometryType == "LINESTRING") + return QGis::WKBLineString25D; + if (geometryType == "POLYGON") + return QGis::WKBPolygon25D; + if (geometryType == "MULTIPOINT") + return QGis::WKBMultiPoint25D; + if (geometryType == "MULTILINESTRING") + return QGis::WKBMultiLineString25D; + if (geometryType == "MULTIPOLYGON") + return QGis::WKBMultiPolygon25D; + else + return QGis::WKBUnknown; + } + else + { + if (geometryType == "POINT") + return QGis::WKBPoint; + if (geometryType == "LINESTRING") + return QGis::WKBLineString; + if (geometryType == "POLYGON") + return QGis::WKBPolygon; + if (geometryType == "MULTIPOINT") + return QGis::WKBMultiPoint; + if (geometryType == "MULTILINESTRING") + return QGis::WKBMultiLineString; + if (geometryType == "MULTIPOLYGON") + return QGis::WKBMultiPolygon; + else + return QGis::WKBUnknown; + } +} + + +QgsVectorLayerImport::ImportError QgsMssqlProvider::createEmptyLayer( + const QString& uri, + const QgsFieldMap &fields, + QGis::WkbType wkbType, + const QgsCoordinateReferenceSystem *srs, + bool overwrite, + QMap *oldToNewAttrIdxMap, + QString *errorMessage, + const QMap *options ) +{ + Q_UNUSED( options ); + + // populate members from the uri structure + QgsDataSourceURI dsUri( uri ); + + // connect to database + QSqlDatabase db = QgsMssqlProvider::GetDatabase( dsUri.service(), + dsUri.host(), dsUri.database(), dsUri.username(), dsUri.password() ); + + if ( !QgsMssqlProvider::OpenDatabase( db ) ) + { + if ( errorMessage ) + *errorMessage = db.lastError( ).text( ); + return QgsVectorLayerImport::ErrConnectionFailed; + } + + QString dbName = dsUri.database(); + + QString schemaName = dsUri.schema(); + QString tableName = dsUri.table(); + + QString geometryColumn = dsUri.geometryColumn(); + + QString primaryKey = dsUri.keyColumn(); + QString primaryKeyType; + + if ( schemaName.isEmpty() ) + schemaName = "dbo"; + + if ( geometryColumn.isEmpty() ) + geometryColumn = "qgs_geometry"; + + if ( primaryKey.isEmpty() ) + primaryKey = "qgs_fid"; + + // 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 = "qgs_fid"; + for ( QgsFieldMap::const_iterator fldIt = fields.begin(); fldIt != fields.end(); ++fldIt ) + { + if ( fldIt.value().name() == pk ) + { + // 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(); + } + } + } + } + + // if the field doesn't not exist yet, create it as a serial field + if ( primaryKeyType.isEmpty() ) + primaryKeyType = "serial"; + + QString sql; + QSqlQuery q = QSqlQuery(db); + q.setForwardOnly(true); + + // initialize metadata tables (same as OGR SQL) + sql = QString("IF NOT EXISTS (SELECT * FROM sys.objects WHERE " + "object_id = OBJECT_ID(N'[dbo].[geometry_columns]') AND type in (N'U')) " + "CREATE TABLE geometry_columns (f_table_catalog varchar(128) not null, " + "f_table_schema varchar(128) not null, f_table_name varchar(256) not null, " + "f_geometry_column varchar(256) not null, coord_dimension integer not null, " + "srid integer not null, geometry_type varchar(30) not null, " + "CONSTRAINT geometry_columns_pk PRIMARY KEY (f_table_catalog, " + "f_table_schema, f_table_name, f_geometry_column));\n" + "IF NOT EXISTS (SELECT * FROM sys.objects " + "WHERE object_id = OBJECT_ID(N'[dbo].[spatial_ref_sys]') AND type in (N'U')) " + "CREATE TABLE spatial_ref_sys (srid integer not null " + "PRIMARY KEY, auth_name varchar(256), auth_srid integer, srtext varchar(2048), proj4text varchar(2048))"); + if (!q.exec( sql )) + { + if ( errorMessage ) + *errorMessage = q.lastError( ).text( ); + return QgsVectorLayerImport::ErrCreateLayer; + } + + // set up spatial reference id + int srid = 0; + if (srs->isValid()) + { + srid = srs->srsid(); + QString auth_srid = "null"; + QString auth_name = "null"; + QStringList sl = srs->authid().split(':'); + if (sl.length() == 2) + { + auth_name = "'" + sl[0] + "'"; + auth_srid = sl[1]; + } + sql = QString("IF NOT EXISTS (SELECT * FROM spatial_ref_sys WHERE srid=%1) INSERT INTO spatial_ref_sys (srid, auth_name, auth_srid, srtext, proj4text) VALUES (%1, %2, %3, '%4', '%5')") + .arg( srs->srsid() ) + .arg( auth_name ) + .arg( auth_srid ) + .arg( srs->toWkt() ) + .arg( srs->toProj4() ); + if (!q.exec( sql )) + { + if ( errorMessage ) + *errorMessage = q.lastError( ).text( ); + return QgsVectorLayerImport::ErrCreateLayer; + } + } + + // get wkb type and dimension + QString geometryType; + int dim = 2; + mssqlWkbTypeAndDimension( wkbType, geometryType, dim ); + + if (overwrite) + { + // remove the old table with the same name + sql = QString("IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[%1].[%2]') AND type in (N'U')) BEGIN DROP TABLE [%1].[%2] DELETE FROM geometry_columns where f_table_schema = '%1' and f_table_name = '%2' END;") + .arg( schemaName ).arg( tableName ); + if (!q.exec( sql )) + { + if ( errorMessage ) + *errorMessage = q.lastError( ).text( ); + return QgsVectorLayerImport::ErrCreateLayer; + } + } + + sql = QString( "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[%1].[%2]') AND type in (N'U')) DROP TABLE [%1].[%2]\n" + "CREATE TABLE [%1].[%2]([%3] [int] IDENTITY(1,1) NOT NULL, [%4] [geometry] NULL CONSTRAINT [PK_%2] PRIMARY KEY CLUSTERED ( [%3] ASC ))\n" + "DELETE FROM geometry_columns WHERE f_table_schema = '%1' AND f_table_name = '%2'\n" + "INSERT INTO [geometry_columns] ([f_table_catalog], [f_table_schema] ,[f_table_name], " + "[f_geometry_column],[coord_dimension],[srid],[geometry_type]) VALUES ('%5', '%1', '%2', '%4', %6, %7, '%8')") + .arg( schemaName ) + .arg( tableName ) + .arg( primaryKey ) + .arg( geometryColumn ) + .arg( dbName ) + .arg( QString::number(dim) ) + .arg( QString::number(srid) ) + .arg( geometryType ); + + if (!q.exec( sql )) + { + if ( errorMessage ) + *errorMessage = q.lastError( ).text( ); + return QgsVectorLayerImport::ErrCreateLayer; + } + + // clear any resources hold by the query + q.clear(); + + // use the provider to edit the table + dsUri.setDataSource( schemaName, tableName, geometryColumn, QString(), primaryKey ); + QgsMssqlProvider *provider = new QgsMssqlProvider( dsUri.uri() ); + if ( !provider->isValid() ) + { + if ( errorMessage ) + *errorMessage = QObject::tr( "Loading of the mssql provider failed" ); + + delete provider; + return QgsVectorLayerImport::ErrInvalidLayer; + } + + // add fields to the layer + if ( oldToNewAttrIdxMap ) + oldToNewAttrIdxMap->clear(); + + if ( fields.size() > 0 ) + { + int offset = geometryColumn.isEmpty() ? 0 : 1; + + // get the list of fields + QList flist; + for ( QgsFieldMap::const_iterator fldIt = fields.begin(); fldIt != fields.end(); ++fldIt ) + { + QgsField fld = fldIt.value(); + if ( fld.name() == primaryKey ) + { + oldToNewAttrIdxMap->insert( fldIt.key(), 0 ); + continue; + } + + if ( fld.name() == geometryColumn ) + { + // Found a field with the same name of the geometry column. Skip it! + continue; + } + + if ( !convertField( fld ) ) + { + if ( errorMessage ) + *errorMessage = QObject::tr( "Unsupported type for field %1" ).arg( fld.name() ); + + delete provider; + return QgsVectorLayerImport::ErrAttributeTypeUnsupported; + } + + flist.append( fld ); + if ( oldToNewAttrIdxMap ) + oldToNewAttrIdxMap->insert( fldIt.key(), offset++ ); + } + + if ( !provider->addAttributes( flist ) ) + { + if ( errorMessage ) + *errorMessage = QObject::tr( "Creation of fields failed" ); + + delete provider; + return QgsVectorLayerImport::ErrAttributeCreationFailed; + } + } + return QgsVectorLayerImport::NoError; +} + + + +/** + * Class factory to return a pointer to a newly created + * QgsMssqlProvider object + */ +QGISEXTERN QgsMssqlProvider *classFactory( const QString *uri ) +{ + return new QgsMssqlProvider( *uri ); +} + +/** Required key function (used to map the plugin to a data store type) +*/ +QGISEXTERN QString providerKey() +{ + return TEXT_PROVIDER_KEY; +} + +/** + * Required description function + */ +QGISEXTERN QString description() +{ + return TEXT_PROVIDER_DESCRIPTION; +} + +/** + * Required isProvider function. Used to determine if this shared library + * is a data provider plugin + */ +QGISEXTERN bool isProvider() +{ + return true; +} + +QGISEXTERN void *selectWidget( QWidget *parent, Qt::WFlags fl ) +{ + return new QgsMssqlSourceSelect( parent, fl ); +} + +QGISEXTERN int dataCapabilities() +{ + return QgsDataProvider::Database; +} + +QGISEXTERN QgsDataItem *dataItem( QString thePath, QgsDataItem *parentItem ) +{ + Q_UNUSED( thePath ); + return new QgsMssqlRootItem( parentItem, "MSSQL", "mssql:" ); +} + +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 QgsMssqlProvider::createEmptyLayer( + uri, fields, wkbType, srs, overwrite, + oldToNewAttrIdxMap, errorMessage, options + ); +} diff --git a/src/providers/mssql/qgsmssqlprovider.h b/src/providers/mssql/qgsmssqlprovider.h new file mode 100644 index 000000000000..9b7d3ca55b7b --- /dev/null +++ b/src/providers/mssql/qgsmssqlprovider.h @@ -0,0 +1,348 @@ +/*************************************************************************** + qgsmssqlprovider.h - Data provider for mssql server + ------------------- + begin : 2011-10-08 + copyright : (C) 2011 by Tamas Szekeres + email : szekerest at gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "qgsvectordataprovider.h" +#include "qgscoordinatereferencesystem.h" +#include "qgsvectorlayerimport.h" + +#include +#include +#include +#include +#include + +class QgsFeature; +class QgsField; +class QFile; +class QTextStream; + +#include "qgsdatasourceuri.h" +#include "qgsgeometry.h" + + +/** +\class QgsMssqlGeometryParser +\brief Geometry parser for SqlGeometry/SqlGeography. +* +*/ + +class QgsMssqlGeometryParser +{ + +protected: + unsigned char* pszData; + unsigned char* pszWkb; + int nWkbLen; + int nWkbMaxLen; + /* byte order */ + char chByteOrder; + /* serialization properties */ + char chProps; + /* point array */ + int nPointSize; + int nPointPos; + int nNumPoints; + /* figure array */ + int nFigurePos; + int nNumFigures; + /* shape array */ + int nShapePos; + int nNumShapes; + int nSRSId; + +protected: + void CopyBytes(void* src, int len); + void CopyPoint(int iPoint); + void ReadPoint(int iShape); + void ReadMultiPoint(int iShape); + void ReadLineString(int iShape); + void ReadMultiLineString(int iShape); + void ReadPolygon(int iShape); + void ReadMultiPolygon(int iShape); + void ReadGeometryCollection(int iShape); + +public: + QgsMssqlGeometryParser(); + unsigned char* ParseSqlGeometry(unsigned char* pszInput, int nLen); + int GetSRSId() { return nSRSId; }; + int GetWkbLen() { return nWkbLen; }; + void DumpMemoryToLog(char* pszMsg, unsigned char* pszInput, int nLen); +}; + + +/** +\class QgsMssqlProvider +\brief Data provider for mssql server. +* +*/ +class QgsMssqlProvider : public QgsVectorDataProvider +{ + Q_OBJECT + + public: + + QgsMssqlProvider( QString uri = QString() ); + + virtual ~QgsMssqlProvider(); + + static QSqlDatabase GetDatabase(QString driver, QString host, QString database, QString username, QString password); + static bool OpenDatabase(QSqlDatabase db); + + /* Implementation of functions from QgsVectorDataProvider */ + + /** + * Returns the permanent storage type for this layer as a friendly name. + */ + virtual QString storageType() const; + + /** + * Sub-layers handled by this provider, in order from bottom to top + * + * Sub-layers are used when the provider's source can combine layers + * it knows about in some way before it hands them off to the provider. + */ + virtual QStringList subLayers() const; + + /** 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 + * + * mFile should be open with the file pointer at the record of the next + * feature, or EOF. The feature found on the current line is parsed. + */ + 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 feature type. + * @return int representing the feature type + */ + virtual QGis::WkbType geometryType() const; + + /** + * Number of features in the layer + * @return long containing number of features + */ + virtual long featureCount() const; + + /** + * Number of attribute fields for a feature in the layer + */ + virtual uint fieldCount() const; + + /** update the extent, feature count, wkb type and srid for this layer */ + void UpdateStatistics(); + + /** + * Return a map of indexes with field names for this layer + * @return map of fields + */ + virtual const QgsFieldMap & fields() const; + + /** Restart reading features from previous select operation */ + virtual void rewind(); + + /** Returns a bitmask containing the supported capabilities + Note, some capabilities may change depending on whether + a spatial filter is active on this provider, so it may + be prudent to check this value per intended operation. + */ + virtual int capabilities() const; + + + /* Implementation of functions from QgsDataProvider */ + + /** 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; + + /** + * Return the extent for this data layer + */ + virtual QgsRectangle extent(); + + /** + * Returns true if this is a valid data source + */ + bool isValid(); + + /**Writes a list of features to the database*/ + virtual bool addFeatures( QgsFeatureList & flist ); + + /**Deletes a feature*/ + virtual bool deleteFeatures( const QgsFeatureIds & id ); + + /** + * Adds new attributes + * @param attributes list of new attributes + * @return true in case of success and false in case of failure + * @note added in 1.2 + */ + virtual bool addAttributes( const QList &attributes ); + + /** + * Deletes existing attributes + * @param attributes a set containing names of attributes + * @return true in case of success and false in case of failure + */ + virtual bool deleteAttributes( const QgsAttributeIds &attributes ); + + /**Changes attribute values of existing features */ + virtual bool changeAttributeValues( const QgsChangedAttributesMap & attr_map ); + + /**Changes existing geometries*/ + virtual bool changeGeometryValues( QgsGeometryMap & geometry_map ); + + /** + * Create a spatial index for the current layer + */ + virtual bool createSpatialIndex(); + + /**Create an attribute index on the datasource*/ + virtual bool createAttributeIndex( int field ); + + /** convert a QgsField to work with MSSQL */ + static bool convertField( QgsField &field ); + + /** 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 + ); + + virtual QgsCoordinateReferenceSystem crs(); + + protected: + /** loads fields from input file to member attributeFields */ + QVariant::Type DecodeODBCType(int sqltype); + void loadFields(); + void loadMetadata(); + + private: + + //! Fields + QgsFieldMap mAttributeFields; + + QgsMssqlGeometryParser parser; + + int mFieldCount; // Note: this includes field count for wkt field + + //! Layer extent + QgsRectangle mExtent; + + bool mValid; + + bool mUseWkb; + bool mSkipFailures; + + int mGeomType; + + long mNumberFeatures; + long mFidCol; + QString mFidColName; + long mSRId; + long mGeometryCol; + QString mGeometryColName; + QString mGeometryColType; + + // QString containing the last reported error message + QString mLastError; + + // Coordinate reference sytem + QgsCoordinateReferenceSystem mCrs; + + QGis::WkbType mWkbType; + + // The database object + QSqlDatabase mDatabase; + + // The current sql query + QSqlQuery mQuery; + + // The current sql statement + QString mStatement; + + // current layer name + QString mSchemaName; + QString mTableName; + // available tables + QStringList mTables; + + // Sets the error messages + void setLastError( QString error ) + { + mLastError = error; + } + + static void mssqlWkbTypeAndDimension( QGis::WkbType wkbType, QString &geometryType, int &dim ); + static QGis::WkbType getWkbType( QString geometryType, int dim ); +}; diff --git a/src/providers/mssql/qgsmssqlsourceselect.cpp b/src/providers/mssql/qgsmssqlsourceselect.cpp new file mode 100644 index 000000000000..45bcc4c216e1 --- /dev/null +++ b/src/providers/mssql/qgsmssqlsourceselect.cpp @@ -0,0 +1,814 @@ +/*************************************************************************** + qgsmssqlsourceselect.cpp + Dialog to select MS SQL layer(s) and add it to the map canvas + ------------------- + begin : 2011-10-08 + copyright : (C) 2011 by Tamas Szekeres + email : szekerest at gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "qgsmssqlsourceselect.h" + +#include "qgslogger.h" +#include "qgsapplication.h" +#include "qgscontexthelp.h" +#include "qgsmssqlprovider.h" +#include "qgsmssqlnewconnection.h" +#include "qgsmanageconnectionsdialog.h" +#include "qgsquerybuilder.h" +#include "qgsdatasourceuri.h" +#include "qgsvectorlayer.h" + +#include +#include +#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 *QgsMssqlSourceSelectDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const +{ + Q_UNUSED( option ); + if ( index.column() == QgsMssqlTableModel::dbtmSql ) + { + QLineEdit *le = new QLineEdit( parent ); + le->setText( index.data( Qt::DisplayRole ).toString() ); + return le; + } + + if ( index.column() == QgsMssqlTableModel::dbtmType && index.data( Qt::UserRole + 1 ).toBool() ) + { + QComboBox *cb = new QComboBox( parent ); + foreach( QGis::GeometryType type, + QList() + << QGis::Point + << QGis::Line + << QGis::Polygon + << QGis::NoGeometry ) + { + cb->addItem( QgsMssqlTableModel::iconForGeomType( type ), QgsMssqlTableModel::displayStringForGeomType( type ), type ); + } + cb->setCurrentIndex( cb->findData( index.data( Qt::UserRole + 2 ).toInt() ) ); + return cb; + } + + if ( index.column() == QgsMssqlTableModel::dbtmPkCol ) + { + QStringList values = index.data( Qt::UserRole + 1 ).toStringList(); + + 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() == QgsMssqlTableModel::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 QgsMssqlSourceSelectDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const +{ + QComboBox *cb = qobject_cast( editor ); + if ( cb ) + { + if ( index.column() == QgsMssqlTableModel::dbtmType ) + { + QGis::GeometryType type = ( QGis::GeometryType ) cb->itemData( cb->currentIndex() ).toInt(); + + model->setData( index, QgsMssqlTableModel::iconForGeomType( type ), Qt::DecorationRole ); + model->setData( index, type != QGis::UnknownGeometry ? QgsMssqlTableModel::displayStringForGeomType( type ) : tr( "Select..." ) ); + model->setData( index, type, Qt::UserRole + 2 ); + } + else if ( index.column() == QgsMssqlTableModel::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() ); +} + +QgsMssqlSourceSelect::QgsMssqlSourceSelect( QWidget *parent, Qt::WFlags fl, bool managerMode, bool embeddedMode ) + : QDialog( parent, fl ) + , mManagerMode( managerMode ) + , mEmbeddedMode( embeddedMode ) + , mColumnTypeThread( NULL ) +{ + setupUi( this ); + + setWindowTitle( tr( "Add MS SQL Table(s)" ) ); + + if ( mEmbeddedMode ) + { + buttonBox->button( QDialogButtonBox::Close )->hide(); + } + + 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( "Schema" ) ); + 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 ); + + mTablesTreeView->setModel( &mProxyModel ); + mTablesTreeView->setSortingEnabled( true ); + mTablesTreeView->setEditTriggers( QAbstractItemView::CurrentChanged ); + mTablesTreeView->setItemDelegate( new QgsMssqlSourceSelectDelegate( this ) ); + + QSettings settings; + mTablesTreeView->setSelectionMode( settings.value( "/qgis/addMSSQLDC", 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/MSSQLSourceSelect/geometry" ).toByteArray() ); + + for ( int i = 0; i < mTableModel.columnCount(); i++ ) + { + mTablesTreeView->setColumnWidth( i, settings.value( QString( "/Windows/MSSQLSourceSelect/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 ); + + cbxAllowGeometrylessTables->setDisabled( true ); +} +/** Autoconnected SLOTS **/ +// Slot for adding a new connection +void QgsMssqlSourceSelect::on_btnNew_clicked() +{ + QgsMssqlNewConnection *nc = new QgsMssqlNewConnection( this ); + if ( nc->exec() ) + { + populateConnectionList(); + emit connectionsChanged(); + } + delete nc; +} +// Slot for deleting an existing connection +void QgsMssqlSourceSelect::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; + + QgsMssqlSourceSelect::deleteConnection( cmbConnections->currentText() ); + + populateConnectionList(); + emit connectionsChanged(); +} + +void QgsMssqlSourceSelect::deleteConnection( QString name ) +{ + QString key = "/MSSQL/connections/" + name; + QSettings settings; + settings.remove( key + "/service" ); + settings.remove( key + "/host" ); + settings.remove( key + "/database" ); + settings.remove( key + "/username" ); + settings.remove( key + "/password" ); + settings.remove( key + "/geometryColumns" ); + settings.remove( key + "/allowGeometrylessTables" ); + settings.remove( key + "/useEstimatedMetadata" ); + settings.remove( key + "/saveUsername" ); + settings.remove( key + "/savePassword" ); + settings.remove( key ); +} + +void QgsMssqlSourceSelect::on_btnSave_clicked() +{ + QgsManageConnectionsDialog dlg( this, QgsManageConnectionsDialog::Export, QgsManageConnectionsDialog::MSSQL ); + dlg.exec(); +} + +void QgsMssqlSourceSelect::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::MSSQL, fileName ); + dlg.exec(); + populateConnectionList(); +} + +// Slot for editing a connection +void QgsMssqlSourceSelect::on_btnEdit_clicked() +{ + QgsMssqlNewConnection *nc = new QgsMssqlNewConnection( this, cmbConnections->currentText() ); + if ( nc->exec() ) + { + populateConnectionList(); + emit connectionsChanged(); + } + delete nc; +} + +/** End Autoconnected SLOTS **/ + +// Remember which database is selected +void QgsMssqlSourceSelect::on_cmbConnections_activated( int ) +{ + // Remember which database was selected. + QSettings settings; + settings.setValue( "/MSSQL/connections/selected", cmbConnections->currentText() ); + + cbxAllowGeometrylessTables->blockSignals( true ); + cbxAllowGeometrylessTables->setChecked( settings.value( "/MSSQL/connections/" + cmbConnections->currentText() + "/allowGeometrylessTables", false ).toBool() ); + cbxAllowGeometrylessTables->blockSignals( false ); +} + +void QgsMssqlSourceSelect::on_cbxAllowGeometrylessTables_stateChanged( int ) +{ + on_btnConnect_clicked(); +} + +void QgsMssqlSourceSelect::buildQuery() +{ + setSql( mTablesTreeView->currentIndex() ); +} + +void QgsMssqlSourceSelect::on_mTablesTreeView_clicked( const QModelIndex &index ) +{ + mBuildQueryButton->setEnabled( index.parent().isValid() ); +} + +void QgsMssqlSourceSelect::on_mTablesTreeView_doubleClicked( const QModelIndex &index ) +{ + QSettings settings; + if ( settings.value( "/qgis/addMSSQLDC", false ).toBool() ) + { + addTables(); + } + else + { + setSql( index ); + } +} + +void QgsMssqlSourceSelect::on_mSearchTableEdit_textChanged( const QString & text ) +{ + if ( mSearchModeComboBox->currentText() == tr( "Wildcard" ) ) + { + mProxyModel._setFilterWildcard( text ); + } + else if ( mSearchModeComboBox->currentText() == tr( "RegExp" ) ) + { + mProxyModel._setFilterRegExp( text ); + } +} + +void QgsMssqlSourceSelect::on_mSearchColumnComboBox_currentIndexChanged( const QString & text ) +{ + if ( text == tr( "All" ) ) + { + mProxyModel.setFilterKeyColumn( -1 ); + } + else if ( text == tr( "Schema" ) ) + { + mProxyModel.setFilterKeyColumn( QgsMssqlTableModel::dbtmSchema ); + } + else if ( text == tr( "Table" ) ) + { + mProxyModel.setFilterKeyColumn( QgsMssqlTableModel::dbtmTable ); + } + else if ( text == tr( "Type" ) ) + { + mProxyModel.setFilterKeyColumn( QgsMssqlTableModel::dbtmType ); + } + else if ( text == tr( "Geometry column" ) ) + { + mProxyModel.setFilterKeyColumn( QgsMssqlTableModel::dbtmGeomCol ); + } + else if ( text == tr( "Primary key column" ) ) + { + mProxyModel.setFilterKeyColumn( QgsMssqlTableModel::dbtmPkCol ); + } + else if ( text == tr( "SRID" ) ) + { + mProxyModel.setFilterKeyColumn( QgsMssqlTableModel::dbtmSrid ); + } + else if ( text == tr( "Sql" ) ) + { + mProxyModel.setFilterKeyColumn( QgsMssqlTableModel::dbtmSql ); + } +} + +void QgsMssqlSourceSelect::on_mSearchModeComboBox_currentIndexChanged( const QString & text ) +{ + Q_UNUSED( text ); + on_mSearchTableEdit_textChanged( mSearchTableEdit->text() ); +} + +void QgsMssqlSourceSelect::setLayerType( QgsMssqlLayerProperty layerProperty ) +{ + QgsDebugMsg( "entering." ); + mTableModel.setGeometryTypesForTable( layerProperty ); +} + +QgsMssqlSourceSelect::~QgsMssqlSourceSelect() +{ + if ( mColumnTypeThread ) + { + mColumnTypeThread->stop(); + mColumnTypeThread->wait(); + } + + QSettings settings; + settings.setValue( "/Windows/MSSQLSourceSelect/geometry", saveGeometry() ); + + for ( int i = 0; i < mTableModel.columnCount(); i++ ) + { + settings.setValue( QString( "/Windows/MSSQLSourceSelect/columnWidths/%1" ).arg( i ), mTablesTreeView->columnWidth( i ) ); + } +} + +void QgsMssqlSourceSelect::populateConnectionList() +{ + QSettings settings; + settings.beginGroup( "/MSSQL/connections" ); + QStringList keys = settings.childGroups(); + QStringList::Iterator it = keys.begin(); + cmbConnections->clear(); + while ( it != keys.end() ) + { + cmbConnections->addItem( *it ); + ++it; + } + + 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 QgsMssqlSourceSelect::addTables() +{ + mSelectedTables.clear(); + + foreach( QModelIndex idx, mTablesTreeView->selectionModel()->selection().indexes() ) + { + if ( idx.column() != QgsMssqlTableModel::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, "mssql" ); + accept(); + } +} + +void QgsMssqlSourceSelect::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 + QSettings settings; + QString key = "/MSSQL/connections/" + cmbConnections->currentText(); + QString service = settings.value( key + "/service" ).toString(); + QString host = settings.value( key + "/host" ).toString(); + QString database = settings.value( key + "/database" ).toString(); + 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(); + } + + bool useGeometryColumns = settings.value( key + "/geometryColumns", false ).toBool(); + + bool allowGeometrylessTables = cbxAllowGeometrylessTables->isChecked(); + + mConnInfo = "dbname='" + database + "' host=" + host + " user='" + username + "' password='" + password + "'"; + if (!service.isEmpty()) + mConnInfo += " service='" + service + "'"; + + QSqlDatabase db = QgsMssqlProvider::GetDatabase( service, + host, database, username, password ); + + if ( !QgsMssqlProvider::OpenDatabase( db ) ) + { + // Let user know we couldn't initialise the MSSQL provider + QMessageBox::warning( this, + tr( "MSSQL Provider" ), db.lastError( ).text( ) ); + return; + } + + QString connectionName; + if (service.isEmpty()) + { + if (host.isEmpty()) + { + QMessageBox::warning( this, + tr( "MSSQL Provider" ), "QgsMssqlProvider host name not specified" ); + return; + } + + if (database.isEmpty()) + { + QMessageBox::warning( this, + tr( "MSSQL Provider" ), "QgsMssqlProvider database name not specified" ); + return; + } + connectionName = host + "." + database; + } + else + connectionName = service; + + + // Read supported layers from database + QApplication::setOverrideCursor( Qt::WaitCursor ); + + // build sql statement + QString query("select "); + if (useGeometryColumns) + { + query += "f_table_schema, f_table_name, f_geometry_column, srid, geometry_type from geometry_columns"; + } + else + { + query += "sys.schemas.name, sys.objects.name, sys.columns.name, null, 'GEOMETRY' from sys.columns join sys.types on sys.columns.system_type_id = sys.types.system_type_id and sys.columns.user_type_id = sys.types.user_type_id join sys.objects on sys.objects.object_id = sys.columns.object_id join sys.schemas on sys.objects.schema_id = sys.schemas.schema_id where sys.types.name = 'geometry' or sys.types.name = 'geography' and sys.objects.type = 'U'"; + } + + if (allowGeometrylessTables) + { + query += " union all select sys.schemas.name, sys.objects.name, null, null, 'NONE' from sys.objects join sys.schemas on sys.objects.schema_id = sys.schemas.schema_id where not exists (select * from sys.columns sc1 join sys.types on sc1.system_type_id = sys.types.system_type_id where (sys.types.name = 'geometry' or sys.types.name = 'geography') and sys.objects.object_id = sc1.object_id) and sys.objects.type = 'U'"; + } + + // issue the sql query + QSqlQuery q = QSqlQuery(db); + q.setForwardOnly(true); + q.exec( query ); + + if (q.isActive()) + { + while ( q.next() ) + { + QgsMssqlLayerProperty layer; + layer.schemaName = q.value(0).toString(); + layer.tableName = q.value(1).toString(); + layer.geometryColName = q.value(2).toString(); + layer.srid = q.value(3).toString(); + layer.type = q.value(4).toString(); + layer.pkCols = QStringList(); //TODO + + QString type = layer.type; + QString srid = layer.srid; + + if ( !layer.geometryColName.isNull() ) + { + if ( type == "GEOMETRY" || type.isNull() || srid.isEmpty() ) + { + addSearchGeometryColumn( connectionName, layer ); + type = ""; + srid = ""; + } + } + + layer.type = type; + layer.srid = srid; + mTableModel.addTableEntry( layer ); + } + + if ( mColumnTypeThread ) + { + btnConnect->setText( tr( "Stop" ) ); + mColumnTypeThread->start(); + } + + //if we have only one schema 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 ) ) ) ); + } + } + } + else + { + QApplication::restoreOverrideCursor(); + // Let user know we couldn't retieve tables from the MSSQL provider + QMessageBox::warning( this, + tr( "MSSQL Provider" ), q.lastError( ).text( ) ); + return; + } + + if ( !mColumnTypeThread ) + { + finishList(); + } +} + +void QgsMssqlSourceSelect::finishList() +{ + QApplication::restoreOverrideCursor(); + + if ( cmbConnections->count() > 0 ) + mAddButton->setEnabled( true ); + + mTablesTreeView->sortByColumn( QgsMssqlTableModel::dbtmTable, Qt::AscendingOrder ); + mTablesTreeView->sortByColumn( QgsMssqlTableModel::dbtmSchema, Qt::AscendingOrder ); +} + +void QgsMssqlSourceSelect::columnThreadFinished() +{ + delete mColumnTypeThread; + mColumnTypeThread = 0; + btnConnect->setText( tr( "Connect" ) ); + + finishList(); +} + +QStringList QgsMssqlSourceSelect::selectedTables() +{ + return mSelectedTables; +} + +QString QgsMssqlSourceSelect::connectionInfo() +{ + return mConnInfo; +} + +void QgsMssqlSourceSelect::setSql( const QModelIndex &index ) +{ + if ( !index.parent().isValid() ) + { + QgsDebugMsg( "schema item found" ); + return; + } + + QModelIndex idx = mProxyModel.mapToSource( index ); + QString tableName = mTableModel.itemFromIndex( idx.sibling( idx.row(), QgsMssqlTableModel::dbtmTable ) )->text(); + + QgsVectorLayer *vlayer = new QgsVectorLayer( mTableModel.layerURI( idx, mConnInfo, mUseEstimatedMetadata ), tableName, "mssql" ); + + 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 QgsMssqlSourceSelect::addSearchGeometryColumn( QString connectionName, QgsMssqlLayerProperty layerProperty ) +{ + // store the column details and do the query in a thread + if ( !mColumnTypeThread ) + { + mColumnTypeThread = new QgsMssqlGeomColumnTypeThread( connectionName, mUseEstimatedMetadata ); + + connect( mColumnTypeThread, SIGNAL( setLayerType( QgsMssqlLayerProperty ) ), + this, SLOT( setLayerType( QgsMssqlLayerProperty ) ) ); + connect( this, SIGNAL( addGeometryColumn( QgsMssqlLayerProperty ) ), + mColumnTypeThread, SLOT( addGeometryColumn( QgsMssqlLayerProperty ) ) ); + connect( mColumnTypeThread, SIGNAL( finished() ), + this, SLOT( columnThreadFinished() ) ); + + } + + emit addGeometryColumn( layerProperty ); +} + +QString QgsMssqlSourceSelect::fullDescription( QString schema, QString table, QString column, QString type ) +{ + QString full_desc = ""; + if ( !schema.isEmpty() ) + full_desc = schema + "."; + full_desc += table + " (" + column + ") " + type; + return full_desc; +} + +void QgsMssqlSourceSelect::setConnectionListPosition() +{ + // If possible, set the item currently displayed database + QSettings settings; + QString toSelect = settings.value( "/MSSQL/connections/selected" ).toString(); + cmbConnections->setCurrentIndex( cmbConnections->findText( toSelect ) ); + + if ( cmbConnections->currentIndex() < 0 ) + { + if ( toSelect.isNull() ) + cmbConnections->setCurrentIndex( 0 ); + else + cmbConnections->setCurrentIndex( cmbConnections->count() - 1 ); + } +} + +void QgsMssqlSourceSelect::setSearchExpression( const QString& regexp ) +{ + Q_UNUSED( regexp ); +} + + +QgsMssqlGeomColumnTypeThread::QgsMssqlGeomColumnTypeThread( QString connectionName, bool useEstimatedMetadata ) + : QThread() + , mConnectionName( connectionName ) + , mUseEstimatedMetadata( useEstimatedMetadata ) +{ + qRegisterMetaType( "QgsMssqlLayerProperty" ); +} + +void QgsMssqlGeomColumnTypeThread::addGeometryColumn( QgsMssqlLayerProperty layerProperty ) +{ + layerProperties << layerProperty; +} + +void QgsMssqlGeomColumnTypeThread::stop() +{ + mStopped = true; +} + +void QgsMssqlGeomColumnTypeThread::run() +{ + mStopped = false; + + foreach( QgsMssqlLayerProperty layerProperty, layerProperties ) + { + if ( !mStopped ) + { + QString table; + if ( mUseEstimatedMetadata ) + { + table = QString( "(SELECT TOP %1 [%2] FROM [%3].[%4] WHERE [%2] IS NOT NULL%5) AS t" ) + .arg( 100 ) + .arg( layerProperty.geometryColName ) + .arg( layerProperty.schemaName ) + .arg( layerProperty.tableName ) + .arg( layerProperty.sql.isEmpty() ? "" : QString( " AND (%1)" ).arg( layerProperty.sql ) ); + } + else if ( !layerProperty.schemaName.isEmpty() ) + { + table = QString( "[%1].[%2]%3" ) + .arg( layerProperty.schemaName ) + .arg( layerProperty.tableName ) + .arg( layerProperty.sql.isEmpty() ? "" : QString( " WHERE %1" ).arg( layerProperty.sql ) ); + } + else + { + table = QString( "[%1]%2" ) + .arg( layerProperty.tableName ) + .arg( layerProperty.sql.isEmpty() ? "" : QString( " WHERE %1" ).arg( layerProperty.sql ) ); + } + + QString query = QString( "SELECT DISTINCT CASE WHEN [%1].STGeometryType() IN ('Point', 'MultiPoint') THEN 'POINT' WHEN [%1].STGeometryType() IN ('Linestring', 'MultiLinestring') THEN 'LINESTRING' WHEN [%1].STGeometryType() IN ('Polygon', 'MultiPolygon') THEN 'POLYGON' ELSE UPPER([%1].STGeometryType()) END, [%1].STSrid FROM %2" ) + .arg( layerProperty.geometryColName ) + .arg( table ); + + + // issue the sql query + QSqlDatabase db = QSqlDatabase::database(mConnectionName); + QSqlQuery q = QSqlQuery(db); + q.setForwardOnly(true); + if (!q.exec( query )) + { + QString msg = q.lastError().text(); + QgsDebugMsg( msg ); + } + + QString type; + QString srid; + + if (q.isActive()) + { + QStringList types; + QStringList srids; + + while ( q.next() ) + { + QString type = q.value(0).toString().toUpper(); + QString srid = q.value(1).toString(); + + if ( type.isEmpty() ) + continue; + + types << type; + srids << srid; + } + + type = types.join( "," ); + srid = srids.join( "," ); + } + + layerProperty.type = type; + layerProperty.srid = srid; + } + else + { + layerProperty.type = ""; + layerProperty.srid = ""; + } + + // Now tell the layer list dialog box... + emit setLayerType( layerProperty ); + } +} \ No newline at end of file diff --git a/src/providers/mssql/qgsmssqlsourceselect.h b/src/providers/mssql/qgsmssqlsourceselect.h new file mode 100644 index 000000000000..f1929635a7e2 --- /dev/null +++ b/src/providers/mssql/qgsmssqlsourceselect.h @@ -0,0 +1,189 @@ +/*************************************************************************** + qgmssqlsourceselect.h - description + ------------------- + begin : 2011-10-08 + copyright : (C) 2011 by Tamas Szekeres + email : szekerest at gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 QGSMSSQLSOURCESELECT_H +#define QGSMSSQLSOURCESELECT_H + +#include "ui_qgsdbsourceselectbase.h" +#include "qgisgui.h" +#include "qgsdbfilterproxymodel.h" +#include "qgsmssqltablemodel.h" +#include "qgscontexthelp.h" + +#include +#include +#include +#include +#include + +class QPushButton; +class QStringList; +class QgsGeomColumnTypeThread; +class QgisApp; +class QgsPgSourceSelect; + +class QgsMssqlSourceSelectDelegate : public QItemDelegate +{ + Q_OBJECT; + + public: + QgsMssqlSourceSelectDelegate( QObject *parent = NULL ) + : QItemDelegate( parent ) + {} + + QWidget *createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const; + void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const; +}; + +// 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 QgsMssqlGeomColumnTypeThread : public QThread +{ + Q_OBJECT + public: + QgsMssqlGeomColumnTypeThread( QString connectionName, bool useEstimatedMetadata ); + + // These functions get the layer types and pass that information out + // by emitting the setLayerType() signal. + virtual void run(); + + signals: + void setLayerType( QgsMssqlLayerProperty layerProperty ); + + public slots: + void addGeometryColumn( QgsMssqlLayerProperty layerProperty ); + void stop(); + + private: + QgsMssqlGeomColumnTypeThread() {} + + QString mConnectionName; + bool mUseEstimatedMetadata; + bool mStopped; + QList layerProperties; +}; + + +/*! \class QgsMssqlSourceSelect + * \brief Dialog to create connections and add tables from MS SQL. + * + * This dialog allows the user to define and save connection information + * for MS SQL databases. The user can then connect and add + * tables from the database to the map canvas. + */ +class QgsMssqlSourceSelect : public QDialog, private Ui::QgsDbSourceSelectBase +{ + Q_OBJECT + + public: + + //! static function to delete a connection + static void deleteConnection( QString key ); + + //! Constructor + QgsMssqlSourceSelect( QWidget *parent = 0, Qt::WFlags fl = QgisGui::ModalDialogFlags, bool managerMode = false, bool embeddedMode = false ); + //! Destructor + ~QgsMssqlSourceSelect(); + //! 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( QgsMssqlLayerProperty ); + + 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 setSql( const QModelIndex& index ); + //! Store the selected database + void on_cmbConnections_activated( int ); + void setLayerType( QgsMssqlLayerProperty 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( QString connectionName, QgsMssqlLayerProperty 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 + QgsMssqlGeomColumnTypeThread* mColumnTypeThread; + QString mConnInfo; + QStringList mSelectedTables; + bool mUseEstimatedMetadata; + // Storage for the range of layer type icons + QMap > mLayerIcons; + + //! Model that acts as datasource for mTableTreeWidget + QgsMssqlTableModel mTableModel; + QgsDbFilterProxyModel mProxyModel; + + QPushButton *mBuildQueryButton; + QPushButton *mAddButton; + + void finishList(); +}; + +#endif // QGSMSSQLSOURCESELECT_H diff --git a/src/providers/mssql/qgsmssqltablemodel.cpp b/src/providers/mssql/qgsmssqltablemodel.cpp new file mode 100644 index 000000000000..33950ee77782 --- /dev/null +++ b/src/providers/mssql/qgsmssqltablemodel.cpp @@ -0,0 +1,431 @@ +/*************************************************************************** + qgsmssqltablemodel.cpp - description + ------------------- + begin : 2011-10-08 + copyright : (C) 2011 by Tamas Szekeres + email : szekerest at gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "qgsmssqltablemodel.h" +#include "qgsdataitem.h" +#include "qgslogger.h" +#include "qgsdatasourceuri.h" + +QgsMssqlTableModel::QgsMssqlTableModel() + : QStandardItemModel() + , mTableCount( 0 ) +{ + QStringList headerLabels; + headerLabels << tr( "Schema" ); + 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 ); +} + +QgsMssqlTableModel::~QgsMssqlTableModel() +{ +} + +void QgsMssqlTableModel::addTableEntry( QgsMssqlLayerProperty layerProperty ) +{ + QgsDebugMsg( QString( "%1.%2.%3 type=%4 srid=%5 pk=%6 sql=%7" ) + .arg( layerProperty.schemaName ) + .arg( layerProperty.tableName ) + .arg( layerProperty.geometryColName ) + .arg( layerProperty.type ) + .arg( layerProperty.srid ) + .arg( layerProperty.pkCols.join( "," ) ) + .arg( layerProperty.sql ) ); + + // is there already a root item with the given scheme Name? + QStandardItem *schemaItem; + QList schemaItems = findItems( layerProperty.schemaName, Qt::MatchExactly, dbtmSchema ); + + // there is already an item for this schema + if ( schemaItems.size() > 0 ) + { + schemaItem = schemaItems.at( dbtmSchema ); + } + else + { + // create a new toplevel item for this schema + schemaItem = new QStandardItem( layerProperty.schemaName ); + schemaItem->setFlags( Qt::ItemIsEnabled ); + invisibleRootItem()->setChild( invisibleRootItem()->rowCount(), schemaItem ); + } + + QGis::GeometryType geomType = QgsMssqlTableModel::geomTypeFromMssql( layerProperty.type ); + if ( geomType == QGis::UnknownGeometry && layerProperty.geometryColName.isEmpty() ) + { + geomType = QGis::NoGeometry; + } + + bool needToDetect = geomType == QGis::UnknownGeometry && layerProperty.type != "GEOMETRYCOLLECTION"; + + QList childItemList; + + QStandardItem *schemaNameItem = new QStandardItem( layerProperty.schemaName ); + schemaNameItem->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); + + QStandardItem *typeItem = new QStandardItem( iconForGeomType( geomType ), + needToDetect + ? tr( "Detecting..." ) + : QgsMssqlTableModel::displayStringForGeomType( geomType ) ); + typeItem->setData( needToDetect, Qt::UserRole + 1 ); + typeItem->setData( geomType, Qt::UserRole + 2 ); + + QStandardItem *tableItem = new QStandardItem( layerProperty.tableName ); + QStandardItem *geomItem = new QStandardItem( layerProperty.geometryColName ); + QStandardItem *sridItem = new QStandardItem( layerProperty.srid ); + sridItem->setEditable( false ); + + QString pkText, pkCol = ""; + switch ( layerProperty.pkCols.size() ) + { + case 0: pkText = ""; break; + case 1: pkText = layerProperty.pkCols[0]; pkCol = pkText; break; + default: pkText = tr( "Select..." ); break; + } + + QStandardItem *pkItem = new QStandardItem( pkText ); + if ( pkText == tr( "Select..." ) ) + pkItem->setFlags( pkItem->flags() | Qt::ItemIsEditable ); + + pkItem->setData( layerProperty.pkCols, Qt::UserRole + 1 ); + pkItem->setData( pkCol, Qt::UserRole + 2 ); + + 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 ); + + childItemList << schemaNameItem; + childItemList << tableItem; + childItemList << typeItem; + childItemList << geomItem; + childItemList << sridItem; + childItemList << pkItem; + childItemList << selItem; + childItemList << sqlItem; + + bool detailsFromThread = needToDetect || + ( geomType != QGis::NoGeometry && layerProperty.srid.isEmpty() ); + + if ( detailsFromThread || pkText == tr( "Select..." ) ) + { + Qt::ItemFlags flags = Qt::ItemIsSelectable; + if ( detailsFromThread ) + flags |= Qt::ItemIsEnabled; + + foreach( QStandardItem *item, childItemList ) + { + item->setFlags( item->flags() & ~flags ); + } + } + + schemaItem->appendRow( childItemList ); + + ++mTableCount; +} + +void QgsMssqlTableModel::setSql( const QModelIndex &index, const QString &sql ) +{ + if ( !index.isValid() || !index.parent().isValid() ) + { + return; + } + + //find out schema name and table name + QModelIndex schemaSibling = index.sibling( index.row(), dbtmSchema ); + QModelIndex tableSibling = index.sibling( index.row(), dbtmTable ); + QModelIndex geomSibling = index.sibling( index.row(), dbtmGeomCol ); + + if ( !schemaSibling.isValid() || !tableSibling.isValid() || !geomSibling.isValid() ) + { + return; + } + + QString schemaName = itemFromIndex( schemaSibling )->text(); + QString tableName = itemFromIndex( tableSibling )->text(); + QString geomName = itemFromIndex( geomSibling )->text(); + + QList schemaItems = findItems( schemaName, Qt::MatchExactly, dbtmSchema ); + if ( schemaItems.size() < 1 ) + { + return; + } + + QStandardItem* schemaItem = schemaItems.at( dbtmSchema ); + + int n = schemaItem->rowCount(); + for ( int i = 0; i < n; i++ ) + { + QModelIndex currentChildIndex = indexFromItem( schemaItem->child( i, dbtmSchema ) ); + 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; + } + } + } +} + +void QgsMssqlTableModel::setGeometryTypesForTable( QgsMssqlLayerProperty layerProperty ) +{ + QStringList typeList = layerProperty.type.split( ",", QString::SkipEmptyParts ); + QStringList sridList = layerProperty.srid.split( ",", QString::SkipEmptyParts ); + Q_ASSERT( typeList.size() == sridList.size() ); + + //find schema item and table item + QStandardItem* schemaItem; + QList schemaItems = findItems( layerProperty.schemaName, Qt::MatchExactly, dbtmSchema ); + + if ( schemaItems.size() < 1 ) + { + return; + } + + schemaItem = schemaItems.at( 0 ); + + int n = schemaItem->rowCount(); + for ( int i = 0; i < n; i++ ) + { + QModelIndex currentChildIndex = indexFromItem( schemaItem->child( i, dbtmSchema ) ); + if ( !currentChildIndex.isValid() ) + { + continue; + } + + QList row; + + for ( int j = 0; j < dbtmColumns; j++ ) + { + row << itemFromIndex( currentChildIndex.sibling( i, j ) ); + } + + if ( row[ dbtmTable ]->text() == layerProperty.tableName && row[ dbtmGeomCol ]->text() == layerProperty.geometryColName ) + { + row[ dbtmSrid ]->setText( layerProperty.srid ); + + if ( typeList.isEmpty() ) + { + row[ dbtmType ]->setText( tr( "Select..." ) ); + row[ dbtmType ]->setFlags( row[ dbtmType ]->flags() | Qt::ItemIsEditable ); + + row[ dbtmSrid ]->setText( tr( "Enter..." ) ); + row[ dbtmSrid ]->setFlags( row[ dbtmSrid ]->flags() | Qt::ItemIsEditable ); + + foreach( QStandardItem *item, row ) + { + item->setFlags( item->flags() | Qt::ItemIsEnabled ); + } + } + else + { + // update existing row + QGis::GeometryType geomType = QgsMssqlTableModel::geomTypeFromMssql( typeList.at( 0 ) ); + + row[ dbtmType ]->setIcon( iconForGeomType( geomType ) ); + row[ dbtmType ]->setText( QgsMssqlTableModel::displayStringForGeomType( geomType ) ); + row[ dbtmType ]->setData( false, Qt::UserRole + 1 ); + row[ dbtmType ]->setData( geomType, Qt::UserRole + 2 ); + + row[ dbtmSrid ]->setText( sridList.at( 0 ) ); + + Qt::ItemFlags flags = Qt::ItemIsEnabled; + if ( layerProperty.pkCols.size() < 2 ) + flags |= Qt::ItemIsSelectable; + + foreach( QStandardItem *item, row ) + { + item->setFlags( item->flags() | flags ); + } + + for ( int j = 1; j < typeList.size(); j++ ) + { + layerProperty.type = typeList[j]; + layerProperty.srid = sridList[j]; + addTableEntry( layerProperty ); + } + } + } + } +} + +QIcon QgsMssqlTableModel::iconForGeomType( QGis::GeometryType type ) +{ + switch ( type ) + { + case QGis::Point: + return QIcon( QgsDataItem::getThemePixmap( "/mIconPointLayer.png" ) ); + case QGis::Line: + return QIcon( QgsDataItem::getThemePixmap( "/mIconLineLayer.png" ) ); + case QGis::Polygon: + return QIcon( QgsDataItem::getThemePixmap( "/mIconPolygonLayer.png" ) ); + case QGis::NoGeometry: + return QIcon( QgsDataItem::getThemePixmap( "/mIconTableLayer.png" ) ); + default: + return QIcon( QgsDataItem::getThemePixmap( "/mIconLayer.png" ) ); + } +} + +bool QgsMssqlTableModel::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::GeometryType geomType = ( QGis::GeometryType ) idx.sibling( idx.row(), dbtmType ).data( Qt::UserRole + 2 ).toInt(); + + bool ok = geomType != QGis::UnknownGeometry; + + if ( geomType != QGis::NoGeometry ) + idx.sibling( idx.row(), dbtmSrid ).data().toInt( &ok ); + + QStringList pkCols = idx.sibling( idx.row(), dbtmPkCol ).data( Qt::UserRole + 1 ).toStringList(); + if ( pkCols.size() > 0 ) + ok = pkCols.contains( idx.sibling( idx.row(), dbtmPkCol ).data().toString() ); + + 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 QgsMssqlTableModel::layerURI( const QModelIndex &index, QString connInfo, bool useEstimatedMetadata ) +{ + if ( !index.isValid() ) + return QString::null; + + QGis::GeometryType geomType = ( QGis::GeometryType ) itemFromIndex( index.sibling( index.row(), dbtmType ) )->data( Qt::UserRole + 2 ).toInt(); + if ( geomType == QGis::UnknownGeometry ) + // no geometry type selected + return QString::null; + + QStandardItem *pkItem = itemFromIndex( index.sibling( index.row(), dbtmPkCol ) ); + QString pkColumnName = pkItem->data( Qt::UserRole + 2 ).toString(); + + if ( pkItem->data( Qt::UserRole + 1 ).toStringList().size() > 0 && + !pkItem->data( Qt::UserRole + 1 ).toStringList().contains( pkColumnName ) ) + // no valid primary candidate selected + return QString::null; + + QString schemaName = index.sibling( index.row(), dbtmSchema ).data( Qt::DisplayRole ).toString(); + QString tableName = index.sibling( index.row(), dbtmTable ).data( Qt::DisplayRole ).toString(); + + QString geomColumnName; + QString srid; + if ( geomType != QGis::NoGeometry ) + { + 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 ) + 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( schemaName, tableName, geomColumnName, sql, pkColumnName ); + uri.setUseEstimatedMetadata( useEstimatedMetadata ); + uri.setGeometryType( geomType ); + uri.setSrid( srid ); + uri.disableSelectAtId( !selectAtId ); + + return uri.uri(); +} + +QGis::GeometryType QgsMssqlTableModel::geomTypeFromMssql( QString dbType ) +{ + dbType = dbType.toUpper(); + + if ( dbType == "POINT" || dbType == "MULTIPOINT" || dbType == "POINTM" || dbType == "MULTIPOINTM" ) + { + return QGis::Point; + } + else if ( dbType == "LINESTRING" || dbType == "MULTILINESTRING" || dbType == "LINESTRINGM" || dbType == "MULTILINESTRINGM" ) + { + return QGis::Line; + } + else if ( dbType == "POLYGON" || dbType == "MULTIPOLYGON" || dbType == "POLYGONM" || dbType == "MULTIPOLYGONM" ) + { + return QGis::Polygon; + } + else if ( dbType == "NONE" ) + { + return QGis::NoGeometry; + } + else + { + return QGis::UnknownGeometry; + } +} + +QString QgsMssqlTableModel::displayStringForGeomType( QGis::GeometryType type ) +{ + switch ( type ) + { + case QGis::Point: + return tr( "Point" ); + case QGis::Line: + return tr( "Line" ); + case QGis::Polygon: + return tr( "Polygon" ); + case QGis::NoGeometry: + return tr( "No Geometry" ); + case QGis::UnknownGeometry: + return tr( "Unknown" ); + } + + Q_ASSERT( !"unexpected geometryType" ); + return QString::null; +} + diff --git a/src/providers/mssql/qgsmssqltablemodel.h b/src/providers/mssql/qgsmssqltablemodel.h new file mode 100644 index 000000000000..1c6e2afa0484 --- /dev/null +++ b/src/providers/mssql/qgsmssqltablemodel.h @@ -0,0 +1,89 @@ +/*************************************************************************** + qgsmssqltablemodel.h - description + ------------------- + begin : 2011-10-08 + copyright : (C) 2011 by Tamas Szekeres + email : szekerest at gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "qgis.h" + +/** Layer Property structure */ +struct QgsMssqlLayerProperty +{ + // MSSQL layer properties + QString type; + QString schemaName; + QString tableName; + QString geometryColName; + QStringList pkCols; + QString srid; + bool isGeography; + QString sql; +}; + + +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, Schema, Tablename, Geometry Column, Sql*/ +class QgsMssqlTableModel : public QStandardItemModel +{ + Q_OBJECT + public: + QgsMssqlTableModel(); + ~QgsMssqlTableModel(); + + /**Adds entry for one database table to the model*/ + void addTableEntry( QgsMssqlLayerProperty property ); + + /**Sets an sql statement that belongs to a cell specified by a model index*/ + void setSql( const QModelIndex& index, const QString& sql ); + + /**Sets one or more geometry types to a row. In case of several types, additional rows are inserted. + This is for tables where the type is dectected later by thread*/ + void setGeometryTypesForTable( QgsMssqlLayerProperty layerProperty ); + + /**Returns the number of tables in the model*/ + int tableCount() const { return mTableCount; } + + enum columns + { + dbtmSchema = 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 iconForGeomType( QGis::GeometryType type ); + + static QGis::GeometryType geomTypeFromMssql( QString dbType ); + + static QString displayStringForGeomType( QGis::GeometryType type ); + + private: + /**Number of tables in the model*/ + int mTableCount; +}; + diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui index a2344717a2f4..40ad5de785e3 100644 --- a/src/ui/qgisapp.ui +++ b/src/ui/qgisapp.ui @@ -17,7 +17,7 @@ 0 0 1052 - 20 + 21 @@ -147,6 +147,7 @@ + @@ -250,6 +251,7 @@ + @@ -1146,6 +1148,18 @@ Ctrl+Shift+L + + + + :/images/themes/default/mActionAddMssqlLayer.png:/images/themes/default/mActionAddMssqlLayer.png + + + Add MSSQL Spatial Layer... + + + Ctrl+Shift+M + + diff --git a/src/ui/qgsmssqlnewconnectionbase.ui b/src/ui/qgsmssqlnewconnectionbase.ui new file mode 100644 index 000000000000..cbb70be2e52e --- /dev/null +++ b/src/ui/qgsmssqlnewconnectionbase.ui @@ -0,0 +1,295 @@ + + + QgsMssqlNewConnectionBase + + + + 0 + 0 + 311 + 352 + + + + + 0 + 0 + + + + Create a New MS SQL connection + + + true + + + true + + + + 9 + + + 6 + + + + + Connection Information + + + + 0 + + + 5 + + + + + 6 + + + 0 + + + + + 6 + + + 0 + + + + + Name + + + txtName + + + + + + + Provider/DSN + + + txtService + + + + + + + Host + + + txtHost + + + + + + + Database + + + txtDatabase + + + + + + + + + + + + + + Username + + + txtUsername + + + + + + + Password + + + txtPassword + + + + + + + + + 6 + + + 0 + + + + + Name of the new connection + + + + + + + + + + + + + + + + Trusted Connection + + + true + + + + + + + + + + QLineEdit::Password + + + + + + + + + + + 0 + + + + + Save Username + + + + + + + &Test Connect + + + + + + + Save Password + + + + + + + + + Only look in the geometry_columns metadata table + + + true + + + + + + + Also list tables with no geometry + + + + + + + Use estimated table parameters + + + true + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok + + + + + + + + txtName + txtService + txtHost + txtDatabase + txtUsername + txtPassword + chkStoreUsername + chkStorePassword + btnConnect + buttonBox + + + + + buttonBox + rejected() + QgsMssqlNewConnectionBase + reject() + + + 313 + 501 + + + 451 + 312 + + + + + buttonBox + accepted() + QgsMssqlNewConnectionBase + accept() + + + 395 + 501 + + + 450 + 287 + + + + + From f2f65d1d79806903aaece28a2e8523b5f0bc5041 Mon Sep 17 00:00:00 2001 From: szekerest Date: Sun, 18 Mar 2012 11:41:53 +0100 Subject: [PATCH 2/2] PG:Implement handledrop for the postgres provider --- .../postgres/qgspostgresdataitems.cpp | 66 +++++++++++++++++++ src/providers/postgres/qgspostgresdataitems.h | 5 ++ 2 files changed, 71 insertions(+) diff --git a/src/providers/postgres/qgspostgresdataitems.cpp b/src/providers/postgres/qgspostgresdataitems.cpp index b13a6d4b1121..41eb7b1c85ad 100644 --- a/src/providers/postgres/qgspostgresdataitems.cpp +++ b/src/providers/postgres/qgspostgresdataitems.cpp @@ -6,6 +6,8 @@ #include "qgslogger.h" #include "qgsdatasourceuri.h" +#include + // --------------------------------------------------------------------------- QgsPGConnectionItem::QgsPGConnectionItem( QgsDataItem* parent, QString name, QString path ) : QgsDataCollectionItem( parent, name, path ) @@ -157,6 +159,70 @@ void QgsPGConnectionItem::deleteConnection() mParent->refresh(); } +bool QgsPGConnectionItem::handleDrop( const QMimeData * data, Qt::DropAction ) +{ + if ( !QgsMimeDataUtils::isUriList( data ) ) + return false; + + // TODO: probably should show a GUI with settings etc + QgsDataSourceURI uri = QgsPostgresConn::connUri( mName ); + + qApp->setOverrideCursor( Qt::WaitCursor ); + + 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, "qgs_geometry" ); + QgsDebugMsg( "URI " + uri.uri() ); + QgsVectorLayerImport::ImportError err; + QString importError; + err = QgsVectorLayerImport::importLayer( srcLayer, uri.uri(), "postgres", &srcLayer->crs(), false, &importError ); + 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; + } + + qApp->restoreOverrideCursor(); + + if ( hasError ) + { + QMessageBox::warning( 0, tr( "Import to PostGIS database" ), tr( "Failed to import some layers!\n\n" ) + importResults.join( "\n" ) ); + } + else + { + QMessageBox::information( 0, tr( "Import to PostGIS database" ), tr( "Import was successful." ) ); + } + + refresh(); + + return true; +} // --------------------------------------------------------------------------- QgsPGLayerItem::QgsPGLayerItem( QgsDataItem* parent, QString name, QString path, QgsLayerItem::LayerType layerType, QgsPostgresLayerProperty layerProperty ) diff --git a/src/providers/postgres/qgspostgresdataitems.h b/src/providers/postgres/qgspostgresdataitems.h index e24e82fbdeaf..df29ea6a1c56 100644 --- a/src/providers/postgres/qgspostgresdataitems.h +++ b/src/providers/postgres/qgspostgresdataitems.h @@ -5,6 +5,8 @@ #include "qgspostgresconn.h" #include "qgspgsourceselect.h" +#include "qgsmimedatautils.h" +#include "qgsvectorlayerimport.h" class QgsPGRootItem; class QgsPGConnectionItem; @@ -40,6 +42,9 @@ class QgsPGConnectionItem : public QgsDataCollectionItem virtual bool equal( const QgsDataItem *other ); virtual QList actions(); + virtual bool acceptDrop() { return true; } + virtual bool handleDrop( const QMimeData * data, Qt::DropAction action ); + QgsPostgresConn *connection() const { return mConn; } signals: